import { Injectable, Logger } from '@nestjs/common';
import {
  DeleteObjectCommand,
  GetObjectCommand,
  HeadObjectCommand,
  PutObjectCommand,
  S3Client,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { StorageService } from './storage.service';
import type {
  ObjectHead,
  PresignDownloadInput,
  PresignUploadInput,
  PresignedUpload,
} from './storage.types';

// presigned URL 기본 유효 시간(초)
const DEFAULT_UPLOAD_EXPIRES = 300; // 업로드: 5분
const DEFAULT_DOWNLOAD_EXPIRES = 60; // 다운로드: 1분(단기 노출)

// NCP Object Storage 구현체 — S3 호환 API 를 그대로 사용한다.
// 환경변수(SSOT: 루트 .env → docker-compose 주입 / 시크릿은 backend 자체 env):
//   OBJECT_STORAGE_ENDPOINT   예) https://kr.object.ncloudstorage.com
//   OBJECT_STORAGE_REGION     예) kr-standard
//   OBJECT_STORAGE_BUCKET     버킷명
//   OBJECT_STORAGE_ACCESS_KEY 서브계정 Access Key (시크릿)
//   OBJECT_STORAGE_SECRET_KEY 서브계정 Secret Key (시크릿)
@Injectable()
export class NcpObjectStorageService extends StorageService {
  private readonly logger = new Logger(NcpObjectStorageService.name);
  // 설정 누락 시에도 앱은 부팅되며(드라이브 외 기능 정상), 실제 사용 시점에 실패한다.
  private readonly client: S3Client | null;
  private readonly bucketName: string;
  private readonly missing: string[];

  constructor() {
    super();
    const endpoint = process.env.OBJECT_STORAGE_ENDPOINT;
    const region = process.env.OBJECT_STORAGE_REGION || 'kr-standard';
    const bucket = process.env.OBJECT_STORAGE_BUCKET;
    const accessKeyId = process.env.OBJECT_STORAGE_ACCESS_KEY;
    const secretAccessKey = process.env.OBJECT_STORAGE_SECRET_KEY;

    this.bucketName = bucket || '';
    this.missing = [
      ['OBJECT_STORAGE_ENDPOINT', endpoint],
      ['OBJECT_STORAGE_BUCKET', bucket],
      ['OBJECT_STORAGE_ACCESS_KEY', accessKeyId],
      ['OBJECT_STORAGE_SECRET_KEY', secretAccessKey],
    ]
      .filter(([, v]) => !v)
      .map(([k]) => k as string);

    if (this.missing.length > 0) {
      // 부팅은 통과시키되 경고를 남긴다 — 드라이브 API 호출 시 ensureConfigured() 가 막는다.
      this.client = null;
      this.logger.warn(
        `오브젝트 스토리지 미설정(드라이브 비활성): ${this.missing.join(', ')} 누락`,
      );
      return;
    }

    this.client = new S3Client({
      endpoint,
      region,
      // NCP/MinIO 등 S3 호환 스토리지는 path-style 접근이 필요(가상호스트 도메인 미지원)
      forcePathStyle: true,
      credentials: {
        accessKeyId: accessKeyId as string,
        secretAccessKey: secretAccessKey as string,
      },
    });
    this.logger.log(
      `오브젝트 스토리지 초기화: endpoint=${endpoint}, bucket=${this.bucketName}`,
    );
  }

  get bucket(): string {
    return this.bucketName;
  }

  // 설정이 완료되어 실제 호출 가능한지 보장 — 미설정 시 명확한 에러로 막는다.
  private ensureConfigured(): S3Client {
    if (!this.client) {
      throw new Error(
        `오브젝트 스토리지 설정 누락: ${this.missing.join(', ')} 를 환경변수로 지정해 주세요.`,
      );
    }
    return this.client;
  }

  async presignUpload(input: PresignUploadInput): Promise<PresignedUpload> {
    const client = this.ensureConfigured();
    const expiresIn = input.expiresInSeconds ?? DEFAULT_UPLOAD_EXPIRES;
    const command = new PutObjectCommand({
      Bucket: this.bucketName,
      Key: input.key,
      // ContentType 을 서명에 포함 → 클라이언트는 동일 Content-Type 헤더로만 업로드 가능
      ContentType: input.contentType,
    });
    const url = await getSignedUrl(client, command, { expiresIn });
    return {
      url,
      method: 'PUT',
      headers: { 'Content-Type': input.contentType },
      expiresAt: new Date(Date.now() + expiresIn * 1000),
    };
  }

  async presignDownload(input: PresignDownloadInput): Promise<string> {
    const client = this.ensureConfigured();
    const expiresIn = input.expiresInSeconds ?? DEFAULT_DOWNLOAD_EXPIRES;
    const command = new GetObjectCommand({
      Bucket: this.bucketName,
      Key: input.key,
      // 다운로드 강제 + 원본 파일명 복원(비ASCII 안전) + 타입 고정
      ResponseContentDisposition: `attachment; filename*=UTF-8''${encodeURIComponent(
        input.filename,
      )}`,
      ResponseContentType: input.contentType,
    });
    return getSignedUrl(client, command, { expiresIn });
  }

  async headObject(key: string): Promise<ObjectHead | null> {
    const client = this.ensureConfigured();
    try {
      const res = await client.send(
        new HeadObjectCommand({ Bucket: this.bucketName, Key: key }),
      );
      return {
        size: res.ContentLength ?? 0,
        contentType: res.ContentType ?? null,
      };
    } catch (err: unknown) {
      // 미존재(404/NotFound)는 정상 흐름 → null. 그 외 오류는 상위로 전파.
      if (this.isNotFound(err)) return null;
      throw err;
    }
  }

  async deleteObject(key: string): Promise<void> {
    const client = this.ensureConfigured();
    try {
      await client.send(
        new DeleteObjectCommand({ Bucket: this.bucketName, Key: key }),
      );
    } catch (err: unknown) {
      // 이미 없으면 멱등 처리(삭제 목적은 달성). 그 외 오류는 전파.
      if (this.isNotFound(err)) return;
      throw err;
    }
  }

  // AWS SDK v3 의 미존재 응답 판별(상태코드 404 또는 NotFound 계열 name)
  private isNotFound(err: unknown): boolean {
    const e = err as {
      name?: string;
      $metadata?: { httpStatusCode?: number };
    };
    return (
      e?.$metadata?.httpStatusCode === 404 ||
      e?.name === 'NotFound' ||
      e?.name === 'NoSuchKey'
    );
  }
}
