import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
  CreateBucketCommand,
  DeleteObjectsCommand,
  HeadBucketCommand,
  PutBucketCorsCommand,
  PutObjectCommand,
  S3Client
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import {
  assertChatMediaContentType as assertChatMedia,
  contentTypeForStorageKey,
  extensionForChatContentType,
  normalizeChatContentType
} from './chat-media.util';

const ALLOWED_IMAGE_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp', 'image/gif']);

const BROWSER_CORS_ORIGINS = [
  'http://localhost:3000',
  'http://localhost:3002',
  'http://127.0.0.1:3000',
  'http://127.0.0.1:3002'
];

@Injectable()
export class MinioService implements OnModuleInit {
  private readonly logger = new Logger(MinioService.name);
  private internalClient!: S3Client;
  private presignClient!: S3Client;
  private bucket!: string;

  constructor(private readonly config: ConfigService) {}

  private createClient(endpoint: string) {
    const useSsl = this.config.get<string>('MINIO_USE_SSL', 'false') === 'true';
    return new S3Client({
      endpoint: `${useSsl ? 'https' : 'http'}://${endpoint}`,
      region: this.config.get<string>('MINIO_REGION', 'us-east-1'),
      credentials: {
        accessKeyId: this.config.get<string>('MINIO_ACCESS_KEY', 'minioadmin'),
        secretAccessKey: this.config.get<string>('MINIO_SECRET_KEY', 'minioadmin')
      },
      forcePathStyle: true
    });
  }

  async onModuleInit() {
    const internalEndpoint = this.config.get<string>('MINIO_ENDPOINT', 'localhost:9000');
    const publicEndpoint = this.config.get<string>('MINIO_PUBLIC_ENDPOINT', internalEndpoint);
    this.bucket = this.config.get<string>('MINIO_BUCKET', 'lendry-id');

    this.internalClient = this.createClient(internalEndpoint);
    this.presignClient = this.createClient(publicEndpoint);

    try {
      await this.internalClient.send(new HeadBucketCommand({ Bucket: this.bucket }));
    } catch {
      await this.internalClient.send(new CreateBucketCommand({ Bucket: this.bucket }));
      this.logger.log(`Создан bucket ${this.bucket}`);
    }

    await this.configureCors();
    this.logger.log(`MinIO: internal=${internalEndpoint}, public=${publicEndpoint}`);
  }

  private async configureCors() {
    try {
      await this.internalClient.send(
        new PutBucketCorsCommand({
          Bucket: this.bucket,
          CORSConfiguration: {
            CORSRules: [
              {
                AllowedHeaders: ['*'],
                AllowedMethods: ['GET', 'PUT', 'POST', 'HEAD'],
                AllowedOrigins: BROWSER_CORS_ORIGINS,
                ExposeHeaders: ['ETag'],
                MaxAgeSeconds: 3600
              }
            ]
          }
        })
      );
    } catch (error) {
      this.logger.warn(`Не удалось настроить CORS для bucket ${this.bucket}: ${error instanceof Error ? error.message : error}`);
    }
  }

  assertImageContentType(contentType: string) {
    if (!ALLOWED_IMAGE_TYPES.has(contentType)) {
      throw new Error('Допустимы только изображения JPEG, PNG, WEBP или GIF');
    }
  }

  assertChatMediaContentType(contentType: string, fileName?: string) {
    return assertChatMedia(contentType, fileName);
  }

  extensionForChatMedia(contentType: string, fileName?: string) {
    return extensionForChatContentType(normalizeChatContentType(contentType, fileName), fileName);
  }

  extensionForContentType(contentType: string) {
    switch (contentType) {
      case 'image/jpeg':
        return 'jpg';
      case 'image/png':
        return 'png';
      case 'image/webp':
        return 'webp';
      case 'image/gif':
        return 'gif';
      case 'audio/mpeg':
        return 'mp3';
      case 'audio/mp4':
        return 'm4a';
      case 'audio/ogg':
        return 'ogg';
      case 'audio/webm':
      case 'video/webm':
        return 'webm';
      case 'audio/wav':
        return 'wav';
      default:
        return 'bin';
    }
  }

  contentTypeForKey(storageKey: string) {
    if (storageKey.startsWith('chat/')) {
      return contentTypeForStorageKey(storageKey);
    }
    if (storageKey.endsWith('.png')) return 'image/png';
    if (storageKey.endsWith('.webp')) return 'image/webp';
    if (storageKey.endsWith('.gif')) return 'image/gif';
    if (storageKey.endsWith('.mp3')) return 'audio/mpeg';
    if (storageKey.endsWith('.m4a')) return 'audio/mp4';
    if (storageKey.endsWith('.ogg')) return 'audio/ogg';
    if (storageKey.endsWith('.webm')) return 'audio/webm';
    if (storageKey.endsWith('.wav')) return 'audio/wav';
    return 'image/jpeg';
  }

  async createPresignedUploadUrl(storageKey: string, contentType: string, expiresInSeconds = 300) {
    const command = new PutObjectCommand({
      Bucket: this.bucket,
      Key: storageKey,
      ContentType: contentType
    });
    const uploadUrl = await getSignedUrl(this.presignClient, command, { expiresIn: expiresInSeconds });
    return {
      uploadUrl,
      expiresAt: new Date(Date.now() + expiresInSeconds * 1000).toISOString()
    };
  }

  getClient() {
    return this.internalClient;
  }

  getBucket() {
    return this.bucket;
  }

  async deleteObjects(keys: string[]) {
    const uniqueKeys = [...new Set(keys.filter(Boolean))];
    if (!uniqueKeys.length) {
      return;
    }

    const chunkSize = 1000;
    for (let index = 0; index < uniqueKeys.length; index += chunkSize) {
      const chunk = uniqueKeys.slice(index, index + chunkSize);
      try {
        await this.internalClient.send(
          new DeleteObjectsCommand({
            Bucket: this.bucket,
            Delete: {
              Objects: chunk.map((Key) => ({ Key })),
              Quiet: true
            }
          })
        );
      } catch (error) {
        this.logger.warn(
          `Не удалось удалить объекты MinIO (${chunk.length} шт.): ${error instanceof Error ? error.message : error}`
        );
      }
    }
  }
}
