import {
  Controller,
  Post,
  Delete,
  Patch,
  Param,
  Body,
  UseGuards,
  UseInterceptors,
  UploadedFile,
  BadRequestException,
  ForbiddenException,
  InternalServerErrorException,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { FileInterceptor } from '@nestjs/platform-express';
import * as multer from 'multer';
import * as path from 'path';
import * as fs from 'fs';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { ApiKeyAuthGuard } from '../auth/guards/api-key-auth.guard';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
import { User } from '../../entities/user.entity';
import { PlatformSettings } from '../../entities/platform-settings.entity';
import { BuildService } from './build.service';
import { MetadataExtractionService } from './services/metadata-extraction.service';
import { SlackService } from '../notifications/slack.service';
import { ConfigService } from '@nestjs/config';

const uploadDir = process.env.UPLOAD_DIR || path.join(process.cwd(), 'uploads');

function ensureUploadDir() {
  if (!fs.existsSync(uploadDir)) {
    fs.mkdirSync(uploadDir, { recursive: true });
  }
}

const storage = multer.diskStorage({
  destination: (_req, _file, cb) => {
    ensureUploadDir();
    cb(null, uploadDir);
  },
  filename: (_req, file, cb) => {
    const ext = path.extname(file.originalname) || '';
    const name = `${Date.now()}-${Math.random().toString(36).slice(2)}${ext}`;
    cb(null, name);
  },
});

@Controller('api/builds')
export class BuildsController {
  constructor(
    private buildService: BuildService,
    private metadataService: MetadataExtractionService,
    private slackService: SlackService,
    private configService: ConfigService,
    @InjectRepository(PlatformSettings)
    private readonly settingsRepo: Repository<PlatformSettings>,
  ) { }

  private async getMaxBuildSizeBytes(user: User): Promise<number> {
    const userLimit = user.maxBuildSizeMb;
    if (userLimit !== null && userLimit !== undefined) {
      return userLimit * 1024 * 1024;
    }
    const settings = await this.settingsRepo.findOne({ where: { id: 'global' } });
    const globalLimit = settings?.maxBuildSizeMb ?? 500;
    return globalLimit * 1024 * 1024;
  }

  private async isCicdEnabled(user: User): Promise<boolean> {
    if (user.cicdEnabled !== null && user.cicdEnabled !== undefined) {
      return user.cicdEnabled;
    }
    const settings = await this.settingsRepo.findOne({ where: { id: 'global' } });
    return settings?.cicdEnabled ?? true;
  }

  private getInstallUrl(shortCode: string): string {
    const isLocal = process.env.NODE_ENV !== 'production' && (!process.env.FRONTEND_URL || process.env.FRONTEND_URL.includes('localhost'));
    const defaultFrontendUrl = isLocal ? 'http://localhost:5173' : 'https://deployhub.workzy.co';
    let frontendUrl = (this.configService.get<string>('FRONTEND_URL') || defaultFrontendUrl).trim();
    if (frontendUrl.endsWith('/')) frontendUrl = frontendUrl.slice(0, -1);
    return `${frontendUrl}/install/${shortCode}`;
  }

  @Post('upload')
  @UseGuards(JwtAuthGuard)
  @UseInterceptors(
    FileInterceptor('file', {
      storage,
      limits: { fileSize: 500 * 1024 * 1024 },
      fileFilter: (_req, file, cb) => {
        const ok = /\.(apk|ipa)$/i.test(file.originalname || '');
        if (!ok) {
          cb(
            new BadRequestException(
              'Unsupported file. Use .apk (Android) or .ipa (iOS).',
            ) as unknown as Error,
            false,
          );
          return;
        }
        cb(null, true);
      },
    }),
  )
  async upload(
    @CurrentUser() user: User,
    @UploadedFile() file: Express.Multer.File,
    @Body('releaseNotes') releaseNotes?: string,
    @Body('password') password?: string,
  ) {
    if (!file) {
      throw new BadRequestException('No file uploaded');
    }
    const maxBytes = await this.getMaxBuildSizeBytes(user);
    if (file.size > maxBytes) {
      try { if (file?.path && fs.existsSync(file.path)) fs.unlinkSync(file.path); } catch { }
      const limitMb = Math.round(maxBytes / (1024 * 1024));
      throw new BadRequestException(`File size exceeds the ${limitMb} MB limit.`);
    }
    
    let metadata: any;
    let appName = 'Unknown App';
    let platform: 'ios' | 'android' = 'android';
    let version = '0.0.0';
    let buildNumber = '0';
    
    let build;
    try {
      // Extract metadata early for error notifications
      platform = this.metadataService.detectPlatform(file.originalname, file.mimetype) || 'android';
      try {
        metadata = await this.metadataService.extract(file.path, platform);
        appName = metadata.appName || 'Unknown App';
        version = platform === 'android' ? metadata.versionName : metadata.version;
        buildNumber = platform === 'android' ? metadata.versionCode : metadata.buildNumber;
      } catch {
        // Continue with defaults if metadata extraction fails
      }
      
      build = await this.buildService.createBuild(
        user,
        file.path,
        file.originalname,
        file.mimetype,
        releaseNotes,
        password,
      );
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Upload failed';
      const errorData: any = err;
      
      // Send failure notification to Slack
      await this.slackService.sendBuildNotification({
        success: false,
        appName,
        platform,
        version,
        buildNumber,
        userName: user.name ?? 'Unknown',
        userEmail: user.email,
        errorMessage: message,
        releaseNotes: releaseNotes || undefined,
      }, user);
      
      if (errorData.code === 'DUPLICATE_BUILD') {
        throw new BadRequestException({
          message,
          code: 'DUPLICATE_BUILD',
          buildId: errorData.buildId,
          shortCode: errorData.shortCode,
        });
      }
      if (errorData.code === 'VERSION_DOWNGRADE' || errorData.code === 'BUILD_DOWNGRADE') {
        throw new BadRequestException({
          message,
          code: errorData.code,
        });
      }
      if (message.includes('refresh token') || message.includes('No refresh token') || message.includes('must re-authenticate')) {
        throw new BadRequestException(
          'Google Drive access expired. Please sign out and sign in again with Google, then try uploading.',
        );
      }
      if (message.includes('Unsupported file') || message.includes('manifest') || message.includes('parse') || message.includes('extract')) {
        throw new BadRequestException(message || 'Could not read app metadata from the file.');
      }
      if (message.includes('Drive') || message.includes('upload failed') || message.includes('permission')) {
        throw new BadRequestException(
          'Google Drive error. Ensure you signed in with Google and have Drive access. Try signing out and in again.',
        );
      }
      throw new InternalServerErrorException(message || 'Build upload failed');
    } finally {
      try {
        if (file?.path && fs.existsSync(file.path)) {
          fs.unlinkSync(file.path);
        }
      } catch {
        // ignore cleanup errors
      }
    }
    if (!build) {
      throw new InternalServerErrorException('Upload failed');
    }
    const withApp = await this.buildService.getBuildById(build.id);
    
    // Send success notification to Slack
    if (withApp && withApp.shortCode) {
      const installUrl = this.getInstallUrl(withApp.shortCode);
      await this.slackService.sendBuildNotification({
        success: true,
        appName: withApp.app.appName,
        platform: withApp.app.platform,
        version: withApp.version,
        buildNumber: withApp.buildNumber,
        userName: user.name ?? 'Unknown',
        userEmail: user.email,
        installUrl,
        releaseNotes: withApp.updateDescription || undefined,
      }, user);
    }
    
    return withApp;
  }

  @Post('ci-upload')
  @UseGuards(ApiKeyAuthGuard)
  @UseInterceptors(
    FileInterceptor('file', {
      storage,
      limits: { fileSize: 500 * 1024 * 1024 },
      fileFilter: (_req, file, cb) => {
        const ok = /\.(apk|ipa)$/i.test(file.originalname || '');
        if (!ok) {
          cb(
            new BadRequestException(
              'Unsupported file. Use .apk (Android) or .ipa (iOS).',
            ) as unknown as Error,
            false,
          );
          return;
        }
        cb(null, true);
      },
    }),
  )
  async ciUpload(
    @CurrentUser() user: User,
    @UploadedFile() file: Express.Multer.File,
    @Body('releaseNotes') releaseNotes?: string,
  ) {
    if (!file) {
      throw new BadRequestException('No file uploaded');
    }
    const cicdOk = await this.isCicdEnabled(user);
    if (!cicdOk) {
      try { if (file?.path && fs.existsSync(file.path)) fs.unlinkSync(file.path); } catch { }
      throw new ForbiddenException('CI/CD uploads are disabled for this account.');
    }
    const maxBytes = await this.getMaxBuildSizeBytes(user);
    if (file.size > maxBytes) {
      try { if (file?.path && fs.existsSync(file.path)) fs.unlinkSync(file.path); } catch { }
      const limitMb = Math.round(maxBytes / (1024 * 1024));
      throw new BadRequestException(`File size exceeds the ${limitMb} MB limit.`);
    }
    
    let metadata: any;
    let appName = 'Unknown App';
    let platform: 'ios' | 'android' = 'android';
    let version = '0.0.0';
    let buildNumber = '0';
    
    let build;
    try {
      // Extract metadata early for error notifications
      platform = this.metadataService.detectPlatform(file.originalname, file.mimetype) || 'android';
      try {
        metadata = await this.metadataService.extract(file.path, platform);
        appName = metadata.appName || 'Unknown App';
        version = platform === 'android' ? metadata.versionName : metadata.version;
        buildNumber = platform === 'android' ? metadata.versionCode : metadata.buildNumber;
      } catch {
        // Continue with defaults if metadata extraction fails
      }
      
      build = await this.buildService.createBuild(
        user,
        file.path,
        file.originalname,
        file.mimetype,
        releaseNotes,
      );
    } catch (err) {
      const message = err instanceof Error ? err.message : 'Upload failed';
      const errorData: any = err;
      
      // Send failure notification to Slack
      await this.slackService.sendBuildNotification({
        success: false,
        appName,
        platform,
        version,
        buildNumber,
        userName: user.name ?? 'CI/CD',
        userEmail: user.email,
        errorMessage: message,
        releaseNotes: releaseNotes || undefined,
      }, user);
      
      if (errorData.code === 'DUPLICATE_BUILD') {
        throw new BadRequestException({
          message,
          code: 'DUPLICATE_BUILD',
          buildId: errorData.buildId,
          shortCode: errorData.shortCode,
        });
      }
      if (errorData.code === 'VERSION_DOWNGRADE' || errorData.code === 'BUILD_DOWNGRADE') {
        throw new BadRequestException({ message, code: errorData.code });
      }
      if (message.includes('refresh token') || message.includes('No refresh token') || message.includes('must re-authenticate')) {
        throw new BadRequestException(
          'Google Drive access expired. The API key owner must re-authenticate via the web UI.',
        );
      }
      throw new InternalServerErrorException(message || 'CI upload failed');
    } finally {
      try {
        if (file?.path && fs.existsSync(file.path)) {
          fs.unlinkSync(file.path);
        }
      } catch {
        // ignore cleanup errors
      }
    }
    if (!build) {
      throw new InternalServerErrorException('Upload failed');
    }
    const withApp = await this.buildService.getBuildById(build.id);
    
    // Send success notification to Slack
    if (withApp && withApp.shortCode) {
      const installUrl = this.getInstallUrl(withApp.shortCode);
      await this.slackService.sendBuildNotification({
        success: true,
        appName: withApp.app.appName,
        platform: withApp.app.platform,
        version: withApp.version,
        buildNumber: withApp.buildNumber,
        userName: user.name ?? 'CI/CD',
        userEmail: user.email,
        installUrl,
        releaseNotes: withApp.updateDescription || undefined,
      }, user);
    }
    
    return withApp;
  }

  @Patch(':id')
  @UseGuards(JwtAuthGuard)
  async updateBuild(
    @CurrentUser() user: User,
    @Param('id') buildId: string,
    @Body('updateDescription') updateDescription: string | undefined,
    @Body('password') password: string | undefined,
    @Body('removePassword') removePassword: boolean | undefined,
  ) {
    const build = await this.buildService.updateBuild(
      buildId,
      user.id,
      typeof updateDescription === 'string' ? updateDescription : undefined,
      password,
      removePassword,
    );
    if (!build) throw new NotFoundException('Build not found');
    const withApp = await this.buildService.getBuildById(build.id);
    return withApp;
  }

  @Delete(':id')
  @UseGuards(JwtAuthGuard)
  async deleteBuild(@CurrentUser() user: User, @Param('id') buildId: string) {
    const deleted = await this.buildService.deleteBuild(buildId, user.id);
    if (!deleted) throw new NotFoundException('Build not found');
    return { success: true };
  }
}
