import { Injectable, Logger } from '@nestjs/common';
import { spawn } from 'child_process';
import { existsSync, mkdirSync, readdirSync } from 'fs';
import path from 'path';
import { DbService } from './db.service';
import { EventsGateway } from './events.gateway';

@Injectable()
export class HandbrakeService {
  private logger = new Logger('HandbrakeService');

  constructor(
    private readonly eventsGateway: EventsGateway,
    private readonly db: DbService,
  ) {}

  /**
   * Check if output file exists (with case-insensitive directory matching)
   * Useful for avoiding creating tasks when output already exists
   */
  outputFileExists(outputPath: string): boolean {
    const outputDir = path.dirname(outputPath);
    const outputFileName = path.basename(outputPath);

    // Check if exact path exists
    if (existsSync(outputPath)) {
      return true;
    }

    // Check for case-insensitive directory match
    const actualDir = this.findExistingDirCaseInsensitive(outputDir);
    if (actualDir) {
      const potentialPath = path.join(actualDir, outputFileName);
      if (existsSync(potentialPath)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Find existing directory with case-insensitive matching
   * Returns the actual path if found, null otherwise
   */
  private findExistingDirCaseInsensitive(dirPath: string): string | null {
    const parentDir = path.dirname(dirPath);
    const targetName = path.basename(dirPath);

    // If parent doesn't exist, can't find anything
    if (!existsSync(parentDir)) {
      return null;
    }

    try {
      const entries = readdirSync(parentDir, { withFileTypes: true });
      const match = entries.find(
        (entry) =>
          entry.isDirectory() &&
          entry.name.toLowerCase() === targetName.toLowerCase(),
      );

      return match ? path.join(parentDir, match.name) : null;
    } catch (err) {
      this.logger.warn(
        `Error checking for case-insensitive directory: ${dirPath}`,
        err,
      );
      return null;
    }
  }

  /**
   * Ensure output directory exists, handling case-insensitive matches
   * Returns the actual directory path to use
   */
  private ensureOutputDirectory(outputPath: string): string {
    const isAbsolute = path.isAbsolute(outputPath);
    const parts = outputPath.split(path.sep).filter((part) => part); // Filter out empty strings
    let currentPath = isAbsolute ? '/' : '';

    // Build path incrementally, checking for case-insensitive matches
    for (let i = 0; i < parts.length; i++) {
      const part = parts[i];

      const proposedPath =
        currentPath === '/' ? `/${part}` : path.join(currentPath, part);

      // Check if directory exists (exact case)
      if (existsSync(proposedPath)) {
        currentPath = proposedPath;
        continue;
      }

      // Check for case-insensitive match
      const existingPath = this.findExistingDirCaseInsensitive(proposedPath);
      if (existingPath) {
        this.logger.log(
          `Found existing directory with different case: ${existingPath} (wanted: ${proposedPath})`,
        );
        currentPath = existingPath;
      } else {
        // Directory doesn't exist, create it
        try {
          mkdirSync(proposedPath, { recursive: false });
          this.logger.log(`Created directory: ${proposedPath}`);
          currentPath = proposedPath;
        } catch (err) {
          throw new Error(
            `Failed to create directory ${proposedPath}: ${err.message}`,
          );
        }
      }
    }

    return currentPath;
  }

  processWithHandbrake(
    input: string,
    output: string,
    preset: string,
    taskId?: number,
  ): Promise<boolean> {
    return new Promise((resolve, reject) => {
      try {
        // Ensure output directory exists, handling case-insensitive matches
        const outputDir = path.dirname(output);
        let actualOutputDir: string;

        try {
          actualOutputDir = this.ensureOutputDirectory(outputDir);
          this.logger.log(`Output directory ready: ${actualOutputDir}`);
        } catch (err) {
          this.logger.error(
            `Failed to prepare output directory: ${outputDir}`,
            err,
          );
          return reject(err);
        }

        // Update output path to use actual directory (in case of case mismatch)
        const actualOutput = path.join(actualOutputDir, path.basename(output));

        const inputName = path.basename(input);
        const outputName = path.basename(actualOutput);
        let progressStarted = false;
        let lastPercent = 0;

        const hb = spawn('HandBrakeCLI', [
          '-i',
          input,
          '-o',
          actualOutput,
          '--preset',
          preset,
        ]);

        hb.stdout.on('data', (data) => {
          const str = data.toString();
          this.logger.debug(`HandBrake stdout: ${str}`);
          console.log('[STDOUT]', str);
          // Parse progress from stdout - try multiple patterns
          let progressMatch = str.match(
            /Encoding: task \d+ of \d+, (\d+(?:\.\d+)?)\s*%/,
          );
          if (!progressMatch) {
            // Try alternative pattern
            progressMatch = str.match(/(\d+(?:\.\d+)?)\s*%/);
          }
          if (progressMatch) {
            const percent = Math.round(parseFloat(progressMatch[1]));
            console.log(
              `[MATCH] Found progress: ${percent}%, lastPercent: ${lastPercent}, taskId: ${taskId}`,
            );
            if (percent !== lastPercent) {
              lastPercent = percent;
              progressStarted = true;

              console.log(`[UPDATE] Progress ${percent}% for task ${taskId}`);

              // Update task progress if we have a task ID
              if (taskId) {
                console.log(
                  `[DB] Updating task ${taskId} progress to ${percent}%`,
                );
                this.db.updateTask(taskId, { progress: percent });
                console.log(`[DB] Update complete`);
              } else {
                console.warn('[WARN] No taskId provided!');
              }

              // Emit progress update
              console.log(
                `[WS] Emitting progress event for task ${taskId}: ${percent}%`,
              );
              this.eventsGateway.emitTaskUpdate({
                type: 'progress',
                taskId,
                task: 'handbrake',
                input,
                output,
                preset,
                progress: percent,
              });
              console.log(`[WS] Emit complete`);
            }
          }
          this.logger.log(str);
        });

        hb.stderr.on('data', (data) => {
          const str = data.toString();
          this.logger.debug(`HandBrake stderr: ${str}`);
          console.log('[STDERR]', str);
          // Parse progress from stderr as fallback - try multiple patterns
          let progressMatch = str.match(
            /Encoding: task \d+ of \d+, (\d+(?:\.\d+)?)\s*%/,
          );
          if (!progressMatch) {
            // Try alternative pattern
            progressMatch = str.match(/(\d+(?:\.\d+)?)\s*%/);
          }
          if (progressMatch && !progressStarted) {
            const percent = Math.round(parseFloat(progressMatch[1]));
            console.log(
              `[STDERR MATCH] Found progress: ${percent}%, lastPercent: ${lastPercent}, taskId: ${taskId}`,
            );
            if (percent !== lastPercent) {
              lastPercent = percent;

              console.log(
                `[STDERR UPDATE] Progress ${percent}% for task ${taskId}`,
              );

              if (taskId) {
                console.log(
                  `[STDERR DB] Updating task ${taskId} progress to ${percent}%`,
                );
                this.db.updateTask(taskId, { progress: percent });
                console.log(`[STDERR DB] Update complete`);
              } else {
                console.warn('[STDERR WARN] No taskId provided!');
              }

              console.log(
                `[STDERR WS] Emitting progress event for task ${taskId}: ${percent}%`,
              );
              this.eventsGateway.emitTaskUpdate({
                type: 'progress',
                taskId,
                task: 'handbrake',
                input,
                output,
                preset,
                progress: percent,
              });
              console.log(`[STDERR WS] Emit complete`);
            }
          }
          this.logger.error(str);
        });

        hb.on('close', (code) => {
          if (code === 0) {
            this.logger.log(
              `Completed "${outputName}" with preset "${preset}"`,
            );

            // Final progress update
            if (taskId) {
              this.db.updateTask(taskId, { progress: 100 });
            }

            this.eventsGateway.emitTaskUpdate({
              type: 'completed',
              taskId,
              task: 'handbrake',
              input,
              output,
              preset,
              success: true,
            });
            resolve(true);
          } else {
            this.logger.error(`HandBrakeCLI exited with code ${code}`);

            this.eventsGateway.emitTaskUpdate({
              type: 'failed',
              taskId,
              task: 'handbrake',
              input,
              output,
              preset,
              success: false,
              error: `Exit code ${code}`,
            });
            reject(new Error(`HandBrakeCLI exited with code ${code}`));
          }
        });
      } catch (err) {
        this.logger.error(
          `Exception in processWithHandbrake: ${err && err.message ? err.message : err}`,
        );
        reject(err);
      }
    });
  }

  getPresetList(): Promise<string[]> {
    return new Promise((resolve, reject) => {
      const hb = spawn('HandBrakeCLI', ['--preset-list']);
      let output = '';
      hb.stdout.on('data', (data) => {
        output += data.toString();
      });
      hb.stderr.on('data', (data) => {
        output += data.toString();
      });
      hb.on('close', (code) => {
        if (code === 0) {
          // Parse the output to extract presets
          const lines = output.split('\n');
          const presets: string[] = [];

          for (const line of lines) {
            // Preset names start with exactly 4 spaces (not 8 for descriptions)
            if (
              line.startsWith('    ') &&
              !line.startsWith('        ') &&
              line.trim().length > 0
            ) {
              presets.push(line.trim());
            }
          }
          resolve(presets);
        } else {
          this.logger.error('Error getting preset list');
          resolve([]);
        }
      });
    });
  }
}
