import { Controller, Get, Param, Query, Res } from '@nestjs/common';
import { ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { firstValueFrom } from 'rxjs';
import { CoreGrpcService } from '../core-grpc.service';
import { buildContentDisposition } from '../media-content-disposition';

type StreamResponse = {
  setHeader: (key: string, value: string) => void;
  status: (code: number) => { json: (body: unknown) => void };
} & NodeJS.WritableStream;

@ApiTags('Скачивание приложений')
@Controller('releases')
export class AppReleaseController {
  private s3Client: S3Client | null = null;

  constructor(private readonly core: CoreGrpcService) {}

  private getS3Client() {
    if (!this.s3Client) {
      const endpoint = process.env.MINIO_ENDPOINT ?? 'localhost:9000';
      const useSsl = process.env.MINIO_USE_SSL === 'true';
      this.s3Client = new S3Client({
        endpoint: `${useSsl ? 'https' : 'http'}://${endpoint}`,
        region: process.env.MINIO_REGION ?? 'us-east-1',
        credentials: {
          accessKeyId: process.env.MINIO_ACCESS_KEY ?? 'minioadmin',
          secretAccessKey: process.env.MINIO_SECRET_KEY ?? 'minioadmin'
        },
        forcePathStyle: true
      });
    }
    return this.s3Client;
  }

  private normalizePlatform(platform: string) {
    const normalized = platform.trim().toUpperCase();
    if (normalized === 'ANDROID' || normalized === 'WINDOWS') {
      return normalized;
    }
    return null;
  }

  @Get()
  @ApiOperation({ summary: 'Публичный список релизов', description: 'Возвращает опубликованные версии приложений для страницы /downloads.' })
  @ApiQuery({ name: 'platform', required: false, enum: ['ANDROID', 'WINDOWS'] })
  list(@Query('platform') platform?: string) {
    return firstValueFrom(this.core.appRelease.ListPublicAppReleases({ platform }));
  }

  @Get('latest/:platform')
  @ApiOperation({ summary: 'Последний релиз платформы', description: 'Метаданные последней опубликованной версии.' })
  @ApiParam({ name: 'platform', enum: ['android', 'windows', 'ANDROID', 'WINDOWS'] })
  latest(@Param('platform') platform: string) {
    const normalized = this.normalizePlatform(platform);
    if (!normalized) {
      return { message: 'Некорректная платформа' };
    }
    return firstValueFrom(this.core.appRelease.GetLatestAppRelease({ platform: normalized }));
  }

  @Get('check')
  @ApiOperation({
    summary: 'Проверка обновления',
    description: 'Для мобильного приложения: сравнивает versionCode с последним релизом.'
  })
  @ApiQuery({ name: 'platform', enum: ['ANDROID', 'WINDOWS'] })
  @ApiQuery({ name: 'versionCode', type: Number })
  check(@Query('platform') platform: string, @Query('versionCode') versionCodeRaw: string) {
    const normalized = this.normalizePlatform(platform);
    const versionCode = Number(versionCodeRaw);
    if (!normalized || !Number.isInteger(versionCode) || versionCode < 1) {
      return { updateAvailable: false };
    }
    return firstValueFrom(this.core.appRelease.CheckAppUpdate({ platform: normalized, versionCode }));
  }

  @Get('download/:platform')
  @ApiOperation({ summary: 'Скачать последний релиз', description: 'Отдаёт файл последней опубликованной версии.' })
  @ApiParam({ name: 'platform', enum: ['android', 'windows', 'ANDROID', 'WINDOWS'] })
  @ApiResponse({ status: 200, description: 'Файл релиза' })
  async downloadLatest(@Param('platform') platform: string, @Res({ passthrough: false }) response: StreamResponse) {
    const normalized = this.normalizePlatform(platform);
    if (!normalized) {
      response.status(400).json({ message: 'Некорректная платформа' });
      return;
    }
    await this.streamRelease(response, normalized);
  }

  @Get('download/:platform/:releaseId')
  @ApiOperation({ summary: 'Скачать конкретный релиз', description: 'Отдаёт файл выбранной опубликованной версии.' })
  async downloadById(
    @Param('platform') platform: string,
    @Param('releaseId') releaseId: string,
    @Res({ passthrough: false }) response: StreamResponse
  ) {
    const normalized = this.normalizePlatform(platform);
    if (!normalized) {
      response.status(400).json({ message: 'Некорректная платформа' });
      return;
    }
    await this.streamRelease(response, normalized, releaseId);
  }

  private async streamRelease(response: StreamResponse, platform: string, releaseId?: string) {
    const resolved = (await firstValueFrom(
      this.core.appRelease.ResolveAppReleaseDownload({ platform, releaseId })
    )) as {
      storageKey: string;
      fileName: string;
      contentType: string;
      fileSize: string;
      sha256: string;
      version: string;
      versionCode: number;
    };

    const bucket = process.env.MINIO_BUCKET ?? 'lendry-id';
    const object = await this.getS3Client().send(
      new GetObjectCommand({
        Bucket: bucket,
        Key: resolved.storageKey
      })
    );

    response.setHeader('Content-Type', resolved.contentType);
    response.setHeader('Content-Length', resolved.fileSize);
    response.setHeader('X-Release-Version', resolved.version);
    response.setHeader('X-Release-Version-Code', String(resolved.versionCode));
    response.setHeader('X-Release-Sha256', resolved.sha256);
    response.setHeader('Cache-Control', 'public, max-age=300');
    response.setHeader('Content-Disposition', buildContentDisposition('attachment', resolved.fileName));

    const body = object.Body;
    if (!body) {
      response.status(404).json({ message: 'Файл релиза не найден' });
      return;
    }

    const stream = body as NodeJS.ReadableStream;
    stream.pipe(response);
  }
}
