import { Controller, Post, Get, Delete, Put, Body, HttpStatus, Res, Param, UseInterceptors, UploadedFile, Inject } from '@nestjs/common';
import { ApiTags, ApiResponse, ApiOperation, ApiParam, ApiConsumes } from '@nestjs/swagger';
import type { Response } from 'express';
import { FileInterceptor } from '@nestjs/platform-express';
import { Public } from '../auth/Public';
import { MediaService } from './MediaService';
import { RequestAvatarGenerationInputDTO } from './dtos/RequestAvatarGenerationInputDTO';
import { RequestAvatarGenerationOutputDTO } from './dtos/RequestAvatarGenerationOutputDTO';
import { UploadMediaInputDTO } from './dtos/UploadMediaInputDTO';
import { UploadMediaOutputDTO } from './dtos/UploadMediaOutputDTO';
import { GetMediaOutputDTO } from './dtos/GetMediaOutputDTO';
import { DeleteMediaOutputDTO } from './dtos/DeleteMediaOutputDTO';
import { GetAvatarOutputDTO } from './dtos/GetAvatarOutputDTO';
import { UpdateAvatarInputDTO } from './dtos/UpdateAvatarInputDTO';
import { UpdateAvatarOutputDTO } from './dtos/UpdateAvatarOutputDTO';
import { ValidateFaceInputDTO } from './dtos/ValidateFaceInputDTO';
import { ValidateFaceOutputDTO } from './dtos/ValidateFaceOutputDTO';
import type { MulterFile } from './types/MulterFile';
import { MediaGenerationService } from '@core/media/domain/services/MediaGenerationService';
import type { Logger } from '@core/shared/application/Logger';
import { MediaReference } from '@core/domain/media/MediaReference';
import { MediaResolverAdapter } from '@adapters/media/MediaResolverAdapter';
import { LOGGER_TOKEN, MEDIA_STORAGE_PORT_TOKEN } from './MediaTokens';
import type { MediaStoragePort } from '@core/media/application/ports/MediaStoragePort';
import path from 'node:path';
import fs from 'node:fs/promises';

type RequestAvatarGenerationInput = RequestAvatarGenerationInputDTO;
type UploadMediaInput = UploadMediaInputDTO;
type UpdateAvatarInput = UpdateAvatarInputDTO;

@ApiTags('media')
@Controller('media')
export class MediaController {
  constructor(
    @Inject(MediaService) private readonly mediaService: MediaService,
    @Inject(MediaGenerationService) private readonly mediaGenerationService: MediaGenerationService,
    @Inject(LOGGER_TOKEN) private readonly logger: Logger,
    @Inject(MediaResolverAdapter) private readonly mediaResolver: MediaResolverAdapter,
    @Inject(MEDIA_STORAGE_PORT_TOKEN) private readonly mediaStorage: MediaStoragePort,
  ) {}

  @Post('avatar/generate')
  @ApiOperation({ summary: 'Request avatar generation' })
  @ApiResponse({ status: 201, description: 'Avatar generation request submitted', type: RequestAvatarGenerationOutputDTO })
  async requestAvatarGeneration(
    @Body() input: RequestAvatarGenerationInput,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Requesting avatar generation', { input });
    const dto: RequestAvatarGenerationOutputDTO = await this.mediaService.requestAvatarGeneration(input);

    if (dto.success) {
      this.logger.info('[MediaController] Avatar generation request successful', { dto });
      res.status(HttpStatus.CREATED).json(dto);
    } else {
      this.logger.warn('[MediaController] Avatar generation request failed', { dto });
      res.status(HttpStatus.BAD_REQUEST).json(dto);
    }
  }

  @Post('upload')
  @UseInterceptors(FileInterceptor('file'))
  @ApiOperation({ summary: 'Upload media file' })
  @ApiConsumes('multipart/form-data')
  @ApiResponse({ status: 201, description: 'Media uploaded successfully', type: UploadMediaOutputDTO })
  async uploadMedia(
    @UploadedFile() file: MulterFile,
    @Body() input: UploadMediaInput,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Uploading media', { filename: file?.originalname, input });
    const dto: UploadMediaOutputDTO = await this.mediaService.uploadMedia({ ...input, file });

    if (dto.success) {
      this.logger.info('[MediaController] Media upload successful', { mediaId: dto.mediaId });
      res.status(HttpStatus.CREATED).json(dto);
    } else {
      this.logger.warn('[MediaController] Media upload failed', { error: dto.error });
      res.status(HttpStatus.BAD_REQUEST).json(dto);
    }
  }

  @Public()
  @Get('teams/:teamId/logo')
  @ApiOperation({ summary: 'Get team logo (dynamically generated)' })
  @ApiParam({ name: 'teamId', description: 'Team ID' })
  async getTeamLogo(
    @Param('teamId') teamId: string,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Generating team logo', { teamId });
    const svg = this.mediaGenerationService.generateTeamLogo(teamId);
    const svgLength = svg.length;
    
    res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
    res.setHeader('Cache-Control', 'public, max-age=86400');
    res.status(HttpStatus.OK).send(svg);
    
    this.logger.info('[MediaController] Team logo generated', { teamId, svgLength });
  }

  @Public()
  @Get('leagues/:leagueId/logo')
  @ApiOperation({ summary: 'Get league logo (dynamically generated)' })
  @ApiParam({ name: 'leagueId', description: 'League ID' })
  async getLeagueLogo(
    @Param('leagueId') leagueId: string,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Generating league logo', { leagueId });
    const svg = this.mediaGenerationService.generateLeagueLogo(leagueId);
    const svgLength = svg.length;
    
    res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
    res.setHeader('Cache-Control', 'public, max-age=86400');
    res.status(HttpStatus.OK).send(svg);
    
    this.logger.info('[MediaController] League logo generated', { leagueId, svgLength });
  }

  @Public()
  @Get('leagues/:leagueId/cover')
  @ApiOperation({ summary: 'Get league cover (dynamically generated)' })
  @ApiParam({ name: 'leagueId', description: 'League ID' })
  async getLeagueCover(
    @Param('leagueId') leagueId: string,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Generating league cover', { leagueId });
    const svg = this.mediaGenerationService.generateLeagueCover(leagueId);
    const svgLength = svg.length;
    
    res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
    res.setHeader('Cache-Control', 'public, max-age=86400');
    res.status(HttpStatus.OK).send(svg);
    
    this.logger.info('[MediaController] League cover generated', { leagueId, svgLength });
  }

  @Public()
  @Get('avatar/:driverId')
  @ApiOperation({ summary: 'Get driver avatar (dynamically generated)' })
  @ApiParam({ name: 'driverId', description: 'Driver ID' })
  async getDriverAvatar(
    @Param('driverId') driverId: string,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Generating driver avatar', { driverId });
    const svg = this.mediaGenerationService.generateDriverAvatar(driverId);
    const svgLength = svg.length;
    
    res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
    res.setHeader('Cache-Control', 'public, max-age=86400');
    res.status(HttpStatus.OK).send(svg);
    
    this.logger.info('[MediaController] Driver avatar generated', { driverId, svgLength });
  }

  @Public()
  @Get('default/:variant')
  @ApiOperation({ summary: 'Get default media asset (PNG)' })
  @ApiParam({ name: 'variant', description: 'Variant name (e.g., male-default-avatar, female-default-avatar, logo)' })
  async getDefaultMedia(
    @Param('variant') variant: string,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Getting default media', { variant });

    // Drivers must use real assets from website public dir.
    // Supported:
    // - male-default-avatar
    // - female-default-avatar
    // - neutral-default-avatar
    if (
      variant === 'male-default-avatar' ||
      variant === 'female-default-avatar' ||
      variant === 'neutral-default-avatar'
    ) {
      const candidates = [`${variant}.jpg`, `${variant}.jpeg`];
      // This needs to work in multiple runtimes:
      // - docker dev (cwd often: /app/apps/api) -> ../website
      // - local tests (cwd often: repo root) -> apps/website
      // Prefer a deterministic directory discovery rather than assuming a single cwd.
      const baseDirs = [
        path.resolve(process.cwd(), 'apps', 'website', 'public', 'images', 'avatars'),
        path.resolve(process.cwd(), '..', 'website', 'public', 'images', 'avatars'),
      ];

      for (const baseDir of baseDirs) {
        for (const filename of candidates) {
          const abs = path.join(baseDir, filename);
          try {
            const bytes = await fs.readFile(abs);
            res.setHeader('Content-Type', filename.endsWith('.png') ? 'image/png' : 'image/jpeg');
            res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
            res.status(HttpStatus.OK).send(bytes);
            this.logger.info('[MediaController] Default avatar served', { variant, filename, baseDir, size: bytes.length });
            return;
          } catch {
            // try next filename/baseDir
          }
        }
      }

      this.logger.warn('[MediaController] Default avatar asset not found', { variant, baseDirs, candidates });
      res.status(HttpStatus.NOT_FOUND).json({ error: 'Default avatar asset not found' });
      return;
    }

    // Fallback: generated PNG for other defaults
    const png = this.mediaGenerationService.generateDefaultPNG(variant);
    res.setHeader('Content-Type', 'image/png');
    res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
    res.status(HttpStatus.OK).send(png);
    this.logger.info('[MediaController] Default media generated', { variant, size: png.length });
  }

  @Public()
  @Get('generated/:type/:id')
  @ApiOperation({ summary: 'Get generated media (SVG)' })
  @ApiParam({ name: 'type', description: 'Media type (team, league, driver)' })
  @ApiParam({ name: 'id', description: 'Entity ID' })
  async getGeneratedMedia(
    @Param('type') type: string,
    @Param('id') id: string,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Generating media', { type, id });
    let svg: string;
    
    // Route to appropriate generator based on type
    if (type === 'team') {
      svg = this.mediaGenerationService.generateTeamLogo(id);
    } else if (type === 'league') {
      svg = this.mediaGenerationService.generateLeagueLogo(id);
    } else if (type === 'driver') {
      svg = this.mediaGenerationService.generateDriverAvatar(id);
    } else {
      // Fallback: generate a generic logo
      svg = this.mediaGenerationService.generateLeagueLogo(`${type}-${id}`);
    }
    
    const svgLength = svg.length;
    
    res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8');
    res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
    res.status(HttpStatus.OK).send(svg);
    
    this.logger.info('[MediaController] Generated media served', { type, id, svgLength });
  }

  @Public()
  @Get('uploaded/:mediaId')
  @ApiOperation({ summary: 'Get uploaded media' })
  @ApiParam({ name: 'mediaId', description: 'Media ID' })
  async getUploadedMedia(
    @Param('mediaId') mediaId: string,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Getting uploaded media', { mediaId });
    
    // Look up the media to get the storage key
    const media = await this.mediaService.getMedia(mediaId);
    if (!media) {
      this.logger.warn('[MediaController] Uploaded media not found', { mediaId });
      res.status(HttpStatus.NOT_FOUND).json({ error: 'Media not found' });
      return;
    }

    // Get the storage key from the media reference
    // The mediaId is used as the storage key
    const storageKey = `uploaded/${mediaId}`;
    
    // Get file bytes from storage
    const bytes = await this.mediaStorage.getBytes!(storageKey);
    if (!bytes) {
      this.logger.warn('[MediaController] Uploaded media file not found', { mediaId, storageKey });
      res.status(HttpStatus.NOT_FOUND).json({ error: 'Media file not found' });
      return;
    }

    // Get metadata to determine content type
    const metadata = await this.mediaStorage.getMetadata!(storageKey);
    const contentType = metadata?.contentType || 'application/octet-stream';

    res.setHeader('Content-Type', contentType);
    res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
    res.status(HttpStatus.OK).send(bytes);
    
    this.logger.info('[MediaController] Uploaded media served', { mediaId, storageKey, size: bytes.length });
  }

  @Public()
  @Get('debug/resolve')
  @ApiOperation({ summary: 'Debug media reference resolution' })
  @ApiResponse({ status: 200, description: 'Resolution debug info' })
  async debugResolve(
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Debug resolve requested');
    
    // Parse query parameters
    const query = res.req.query;
    
    let ref: MediaReference | null = null;
    let refHash: string | null = null;
    let resolvedPath: string | null = null;
    let resolver: string | null = null;
    const notes: string[] = [];
    
    try {
      // Try to construct MediaReference from query params
      if (query.type === 'system-default' && query.variant) {
        const variant = query.variant as 'avatar' | 'logo';
        const avatarVariant = query.avatarVariant as 'male' | 'female' | 'neutral' | undefined;
        ref = MediaReference.createSystemDefault(variant, avatarVariant);
        resolver = 'default';
      } else if (query.type === 'generated' && query.generationRequestId) {
        ref = MediaReference.createGenerated(query.generationRequestId as string);
        resolver = 'generated';
      } else if (query.type === 'uploaded' && query.mediaId) {
        ref = MediaReference.createUploaded(query.mediaId as string);
        resolver = 'uploaded';
      } else if (query.ref) {
        // Try to parse base64url JSON
        try {
          const decoded = Buffer.from(query.ref as string, 'base64').toString('utf-8');
          const props = JSON.parse(decoded);
          ref = MediaReference.fromJSON(props);
          resolver = 'auto-detected';
        } catch (e) {
          notes.push('Failed to parse ref as base64url JSON');
        }
      } else {
        notes.push('No valid query parameters provided');
        notes.push('Expected: type, variant, avatarVariant OR generationRequestId OR mediaId OR ref (base64url)');
      }
      
      if (ref) {
        refHash = ref.hash();
        resolvedPath = await this.mediaResolver.resolve(ref);
        
        if (!resolvedPath) {
          notes.push('Resolver returned null');
        }
      }
      
      this.logger.info('[MediaController] Debug resolve completed', {
        ref: ref ? ref.toJSON() : null,
        refHash,
        resolvedPath,
        resolver,
        notes,
      });
      
      res.status(HttpStatus.OK).json({
        ref: ref ? ref.toJSON() : null,
        refHash,
        resolvedPath,
        resolver,
        notes,
      });
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.error('[MediaController] Debug resolve failed', error instanceof Error ? error : new Error(String(error)));
      res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
        error: errorMessage,
        notes: ['Internal error during resolution'],
      });
    }
  }

  @Public()
  @Get(':mediaId')
  @ApiOperation({ summary: 'Get media by ID' })
  @ApiParam({ name: 'mediaId', description: 'Media ID' })
  @ApiResponse({ status: 200, description: 'Media details', type: GetMediaOutputDTO })
  async getMedia(
    @Param('mediaId') mediaId: string,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Getting media details', { mediaId });
    const dto: GetMediaOutputDTO | null = await this.mediaService.getMedia(mediaId);

    if (dto) {
      this.logger.info('[MediaController] Media details found', { mediaId });
      res.status(HttpStatus.OK).json(dto);
    } else {
      this.logger.warn('[MediaController] Media not found', { mediaId });
      res.status(HttpStatus.NOT_FOUND).json({ error: 'Media not found' });
    }
  }

  @Delete(':mediaId')
  @ApiOperation({ summary: 'Delete media by ID' })
  @ApiParam({ name: 'mediaId', description: 'Media ID' })
  @ApiResponse({ status: 200, description: 'Media deleted', type: DeleteMediaOutputDTO })
  async deleteMedia(
    @Param('mediaId') mediaId: string,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Deleting media', { mediaId });
    const dto: DeleteMediaOutputDTO = await this.mediaService.deleteMedia(mediaId);

    this.logger.info('[MediaController] Media deletion result', { mediaId, success: dto.success });
    res.status(HttpStatus.OK).json(dto);
  }
 
  @Public()
  @Get('avatar/:driverId/details')
  @ApiOperation({ summary: 'Get avatar details for driver' })
  @ApiParam({ name: 'driverId', description: 'Driver ID' })
  @ApiResponse({ status: 200, description: 'Avatar details', type: GetAvatarOutputDTO })
  async getAvatarDetails(
    @Param('driverId') driverId: string,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Getting avatar details', { driverId });
    const dto: GetAvatarOutputDTO | null = await this.mediaService.getAvatar(driverId);

    if (dto) {
      this.logger.info('[MediaController] Avatar details found', { driverId });
      res.status(HttpStatus.OK).json(dto);
    } else {
      this.logger.warn('[MediaController] Avatar not found', { driverId });
      res.status(HttpStatus.NOT_FOUND).json({ error: 'Avatar not found' });
    }
  }
 
  @Put('avatar/:driverId')
  @ApiOperation({ summary: 'Update avatar for driver' })
  @ApiParam({ name: 'driverId', description: 'Driver ID' })
  @ApiResponse({ status: 200, description: 'Avatar updated', type: UpdateAvatarOutputDTO })
  async updateAvatar(
    @Param('driverId') driverId: string,
    @Body() input: UpdateAvatarInput,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Updating avatar', { driverId });
    const dto: UpdateAvatarOutputDTO = await this.mediaService.updateAvatar(driverId, input);

    this.logger.info('[MediaController] Avatar update result', { driverId, success: dto.success });
    res.status(HttpStatus.OK).json(dto);
  }

  @Post('avatar/validate-face')
  @ApiOperation({ summary: 'Validate face photo for avatar generation' })
  @ApiResponse({ status: 200, description: 'Face validation result', type: ValidateFaceOutputDTO })
  async validateFacePhoto(
    @Body() input: ValidateFaceInputDTO,
    @Res() res: Response,
  ): Promise<void> {
    this.logger.debug('[MediaController] Validating face photo');
    const dto: ValidateFaceOutputDTO = await this.mediaService.validateFacePhoto(input);

    if (dto.isValid) {
      this.logger.info('[MediaController] Face validation passed');
      res.status(HttpStatus.OK).json(dto);
    } else {
      this.logger.warn('[MediaController] Face validation failed', { errorMessage: dto.errorMessage });
      res.status(HttpStatus.BAD_REQUEST).json(dto);
    }
  }
}
