import { Injectable } from '@nestjs/common';
import Database from 'better-sqlite3';
import fs from 'fs';
import path from 'path';

@Injectable()
export class DbService {
  // List all files
  listAllFiles() {
    return this.db.prepare('SELECT * FROM files').all();
  }

  // List all files for a dataset
  listFilesForDataset(dataset: string) {
    return this.db
      .prepare('SELECT * FROM files WHERE dataset = ?')
      .all(dataset);
  }
  private db: Database.Database;

  constructor() {
    // Use unified database for all settings/configuration
    // Find project root by traversing up from current directory until we find the root package.json
    let projectRoot = process.cwd();
    while (projectRoot !== path.dirname(projectRoot)) {
      if (fs.existsSync(path.join(projectRoot, 'package.json'))) {
        try {
          const pkg = JSON.parse(
            fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf-8'),
          );
          if (pkg.name === 'watch-finished-turbo') {
            break;
          }
        } catch (e) {
          // ignore
        }
      }
      projectRoot = path.dirname(projectRoot);
    }

    const rootDataPath = path.resolve(projectRoot, 'data/database.db');
    console.log('Database path:', rootDataPath);
    console.log('Project root:', projectRoot);
    console.log('Current working directory:', process.cwd());

    // Ensure the directory exists
    const dir = path.dirname(rootDataPath);
    console.log('Database directory:', dir);

    if (!fs.existsSync(dir)) {
      console.log('Creating database directory...');
      fs.mkdirSync(dir, { recursive: true });
    }

    try {
      this.db = new Database(rootDataPath);
      console.log('Database opened successfully');
      this.migrate();
    } catch (error) {
      console.error('Failed to open database:', error);
      console.error('Database path:', rootDataPath);
      console.error('Directory exists:', fs.existsSync(dir));
      console.error(
        'Directory permissions:',
        fs.statSync(dir).mode.toString(8),
      );
      throw error;
    }
  }

  /**
   * Delete file records older than X days (autoExpireDays from settings)
   * @param days Days to keep (optional, overrides settings)
   */
  deleteExpiredFiles(days?: number): number {
    // Fallback to 180 if no settings available
    const keepDays = days || 180;
    const cutoff = new Date(
      Date.now() - keepDays * 24 * 60 * 60 * 1000,
    ).toISOString();
    const stmt = this.db.prepare('DELETE FROM files WHERE date < ?');
    const info = stmt.run(cutoff);
    return info.changes;
  }

  /**
   * Migrate legacy JSON files to the SQLite database.
   * @param opts Options for migration.
   * @param dbOverride Optional override for db instance.
   */
  migrateJsonToSqlite(
    opts: { datasets?: string[]; dataDir?: string } = {},
    dbOverride?: Database.Database,
  ) {
    const datasets = opts.datasets || [
      'movies',
      'tvshows',
      'kids',
      'pr0n',
      'sports',
    ];
    const dataDir = opts.dataDir || path.join(process.cwd(), 'legacy/data');
    const dbInstance = dbOverride || this.db;

    dbInstance.exec(`
      CREATE TABLE IF NOT EXISTS files (
        dataset TEXT,
        input TEXT,
        output TEXT,
        date TEXT,
        PRIMARY KEY (dataset, input)
      );
    `);

    const insert = dbInstance.prepare(
      'INSERT INTO files (dataset, input, output, date) VALUES (?, ?, ?, ?)',
    );
    for (const dataset of datasets) {
      const filePath = path.join(dataDir, `${dataset}.json`);
      if (!fs.existsSync(filePath)) continue;
      const json = JSON.parse(fs.readFileSync(filePath, 'utf8'));
      if (!json.files || !Array.isArray(json.files)) continue;
      for (const rec of json.files) {
        insert.run(
          dataset,
          rec.input || null,
          rec.output || null,
          rec.date || null,
        );
      }
      // Optionally log: console.log(`Migrated ${json.files.length} records from ${dataset}.json`);
    }
    // Optionally log: console.log('Migration complete.');
  }

  private migrate() {
    // Migration logic from legacy db.js
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS files (
        dataset TEXT,
        input TEXT,
        output TEXT,
        date TEXT,
        PRIMARY KEY (dataset, input)
      );

      CREATE TABLE IF NOT EXISTS tasks (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        type TEXT NOT NULL,
        status TEXT DEFAULT 'pending',
        progress INTEGER DEFAULT 0,
        dataset TEXT,
        input TEXT,
        output TEXT,
        preset TEXT,
        priority INTEGER DEFAULT 0,
        retry_count INTEGER DEFAULT 0,
        max_retries INTEGER,
        error_message TEXT,
        created_at TEXT DEFAULT CURRENT_TIMESTAMP,
        updated_at TEXT DEFAULT CURRENT_TIMESTAMP
      );
    `);

    // Add missing columns to existing tasks table
    this.addMissingColumns();
  }

  private addMissingColumns() {
    const tableInfo = this.db
      .prepare('PRAGMA table_info(tasks)')
      .all() as any[];
    const existingColumns = tableInfo.map((col) => col.name);

    const columnsToAdd = [
      { name: 'dataset', type: 'TEXT' },
      { name: 'input', type: 'TEXT' },
      { name: 'output', type: 'TEXT' },
      { name: 'preset', type: 'TEXT' },
      { name: 'error_message', type: 'TEXT' },
      { name: 'priority', type: 'INTEGER DEFAULT 0' },
      { name: 'retry_count', type: 'INTEGER DEFAULT 0' },
      { name: 'max_retries', type: 'INTEGER' },
    ];

    for (const col of columnsToAdd) {
      if (!existingColumns.includes(col.name)) {
        try {
          this.db.exec(`ALTER TABLE tasks ADD COLUMN ${col.name} ${col.type}`);
          console.log(`Added missing column: ${col.name}`);
        } catch (error) {
          console.warn(`Could not add column ${col.name}:`, error.message);
        }
      }
    }
  }

  findFile(dataset: string, file: string) {
    return this.db
      .prepare('SELECT * FROM files WHERE dataset = ? AND input = ?')
      .get(dataset, file);
  }

  setFile(dataset: string, file: string | any, payload?: any) {
    if (!payload && typeof file === 'object') {
      const rec = file;
      this.db
        .prepare(
          'INSERT INTO files (dataset, input, output, date) VALUES (?, ?, ?, ?)',
        )
        .run(
          dataset,
          rec.input,
          rec.output,
          rec.date ? new Date(rec.date).toISOString() : null,
        );
      return;
    }
    const found = this.findFile(dataset, file as string);
    if (found) {
      this.db
        .prepare(
          'UPDATE files SET output = COALESCE(?, output), date = COALESCE(?, date) WHERE dataset = ? AND input = ?',
        )
        .run(
          payload.output,
          payload.date ? new Date(payload.date).toISOString() : null,
          dataset,
          file,
        );
    } else {
      this.db
        .prepare(
          'INSERT INTO files (dataset, input, output, date) VALUES (?, ?, ?, ?)',
        )
        .run(
          dataset,
          file,
          payload.output,
          payload.date ? new Date(payload.date).toISOString() : null,
        );
    }
  }

  removeFile(dataset: string, file: string, soft = true) {
    if (soft) {
      this.db
        .prepare(
          'UPDATE files SET status = ?, date = ? WHERE dataset = ? AND input = ?',
        )
        .run('deleted', new Date().toISOString(), dataset, file);
    } else {
      this.db
        .prepare('DELETE FROM files WHERE dataset = ? AND input = ?')
        .run(dataset, file);
    }
  }

  clearAllFiles() {
    this.db.prepare('DELETE FROM files').run();
  }

  getAllFiles(dataset: string) {
    return this.db
      .prepare('SELECT * FROM files WHERE dataset = ?')
      .all(dataset);
  }

  getDeletedOlderThan(dataset: string, isoDate: string) {
    return this.db
      .prepare('SELECT * FROM files WHERE dataset = ? AND date < ?')
      .all(dataset, isoDate);
  }

  // Task CRUD methods
  getAllTasks() {
    return this.db
      .prepare('SELECT * FROM tasks ORDER BY created_at DESC')
      .all();
  }

  getPendingTasks(limit: number = 10) {
    return this.db
      .prepare(
        'SELECT * FROM tasks WHERE status = ? ORDER BY priority DESC, created_at ASC LIMIT ?',
      )
      .all('pending', limit);
  }

  getTaskById(id: number) {
    return this.db.prepare('SELECT * FROM tasks WHERE id = ?').get(id);
  }

  getTaskByInput(input: string) {
    return this.db.prepare('SELECT * FROM tasks WHERE input = ?').get(input);
  }

  createTask(task: {
    type: string;
    status?: string;
    progress?: number;
    dataset?: string;
    input?: string;
    output?: string;
    preset?: string;
    priority?: number;
    error_message?: string;
    retry_count?: number;
    max_retries?: number;
  }) {
    const result = this.db
      .prepare(
        `INSERT INTO tasks (type, status, progress, dataset, input, output, preset, priority, retry_count, max_retries, error_message)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
      )
      .run(
        task.type,
        task.status || 'pending',
        task.progress || 0,
        task.dataset || null,
        task.input || null,
        task.output || null,
        task.preset || null,
        task.priority || 0,
        task.retry_count || 0,
        task.max_retries || null,
        task.error_message || null,
      );
    return { id: result.lastInsertRowid, ...task };
  }

  deleteTask(id: number) {
    return this.db.prepare('DELETE FROM tasks WHERE id = ?').run(id);
  }

  updateTask(
    id: number,
    updates: {
      status?: string;
      progress?: number;
      dataset?: string;
      input?: string;
      output?: string;
      preset?: string;
      priority?: number;
      error_message?: string;
      retry_count?: number;
      max_retries?: number;
    },
  ) {
    const setParts = [];
    const values = [];

    if (updates.status !== undefined) {
      setParts.push('status = ?');
      values.push(updates.status);
    }

    if (updates.progress !== undefined) {
      setParts.push('progress = ?');
      values.push(updates.progress);
    }

    if (updates.dataset !== undefined) {
      setParts.push('dataset = ?');
      values.push(updates.dataset);
    }

    if (updates.input !== undefined) {
      setParts.push('input = ?');
      values.push(updates.input);
    }

    if (updates.output !== undefined) {
      setParts.push('output = ?');
      values.push(updates.output);
    }

    if (updates.preset !== undefined) {
      setParts.push('preset = ?');
      values.push(updates.preset);
    }

    if (updates.priority !== undefined) {
      setParts.push('priority = ?');
      values.push(updates.priority);
    }

    if (updates.error_message !== undefined) {
      setParts.push('error_message = ?');
      values.push(updates.error_message);
    }

    if (updates.retry_count !== undefined) {
      setParts.push('retry_count = ?');
      values.push(updates.retry_count);
    }

    if (updates.max_retries !== undefined) {
      setParts.push('max_retries = ?');
      values.push(updates.max_retries);
    }

    if (setParts.length > 0) {
      setParts.push('updated_at = CURRENT_TIMESTAMP');
      values.push(id);

      this.db
        .prepare(`UPDATE tasks SET ${setParts.join(', ')} WHERE id = ?`)
        .run(...values);
    }

    return this.db.prepare('SELECT * FROM tasks WHERE id = ?').get(id);
  }

  // Task maintenance methods
  deleteTasksByStatus(status: string, olderThanDays?: number) {
    let query = 'DELETE FROM tasks WHERE status = ?';
    const params = [status];

    if (olderThanDays !== undefined) {
      query +=
        ' AND created_at < datetime("now", "-' + olderThanDays + ' days")';
    }

    return this.db.prepare(query).run(...params);
  }

  deleteTasksOlderThan(days: number) {
    return this.db
      .prepare(
        'DELETE FROM tasks WHERE created_at < datetime("now", "-' +
          days +
          ' days")',
      )
      .run();
  }

  getTaskStats() {
    const stats = this.db
      .prepare(
        `
        SELECT
          status,
          COUNT(*) as count,
          MIN(created_at) as oldest,
          MAX(created_at) as newest
        FROM tasks
        GROUP BY status
      `,
      )
      .all();

    const total = this.db
      .prepare('SELECT COUNT(*) as total FROM tasks')
      .get() as { total: number };

    return {
      total: total.total,
      byStatus: stats,
    };
  }

  archiveOldTasks(daysOld: number = 30): { changes?: number } {
    // Create archive table if it doesn't exist
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS tasks_archive (
        id INTEGER PRIMARY KEY,
        type TEXT NOT NULL,
        status TEXT NOT NULL,
        progress REAL DEFAULT 0,
        dataset TEXT,
        input TEXT,
        output TEXT,
        preset TEXT,
        priority INTEGER DEFAULT 0,
        retry_count INTEGER DEFAULT 0,
        max_retries INTEGER,
        error_message TEXT,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        archived_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )
    `);

    // Move old completed/failed tasks to archive
    const insertResult = this.db
      .prepare(
        `
      INSERT INTO tasks_archive
      SELECT *, CURRENT_TIMESTAMP as archived_at
      FROM tasks
      WHERE status IN ('completed', 'failed', 'skipped')
      AND created_at < datetime('now', '-${daysOld} days')
    `,
      )
      .run();

    // Delete archived tasks from main table
    const deleteResult = this.db
      .prepare(
        `
      DELETE FROM tasks
      WHERE status IN ('completed', 'failed', 'skipped')
      AND created_at < datetime('now', '-${daysOld} days')
    `,
      )
      .run();

    return { changes: insertResult.changes };
  }

  // Purge all tasks from the database
  purgeAllTasks() {
    const result = this.db.prepare('DELETE FROM tasks').run();
    return result;
  }
}
