/**
 * Database layer for Onebox using SQLite
 * Refactored into repository pattern
 */

import * as plugins from '../plugins.ts';
import type {
  IService,
  IRegistry,
  IRegistryToken,
  ISslCertificate,
  IMetric,
  ILogEntry,
  IUser,
  IPlatformService,
  IPlatformResource,
  TPlatformServiceType,
  IDomain,
  ICertificate,
  ICertRequirement,
  IBackup,
  IBackupSchedule,
  IBackupScheduleUpdate,
  IHostedAppLifecycleRecord,
  IHostedAppLifecycleState,
} from '../types.ts';
import type { TBindValue } from './types.ts';
import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts';
import { MigrationRunner } from './migrations/index.ts';
import { SecretSettingsManager } from './secret-settings.ts';

// Import repositories
import {
  ServiceRepository,
  RegistryRepository,
  CertificateRepository,
  AuthRepository,
  MetricsRepository,
  PlatformRepository,
  BackupRepository,
} from './repositories/index.ts';

export class OneboxDatabase {
  private db: InstanceType<typeof plugins.sqlite.DB> | null = null;
  private dbPath: string;

  // Repositories
  private serviceRepo!: ServiceRepository;
  private registryRepo!: RegistryRepository;
  private certificateRepo!: CertificateRepository;
  private authRepo!: AuthRepository;
  private metricsRepo!: MetricsRepository;
  private platformRepo!: PlatformRepository;
  private backupRepo!: BackupRepository;
  public secretSettings!: SecretSettingsManager;

  constructor(dbPath = './.nogit/onebox.db') {
    this.dbPath = dbPath;
  }

  /**
   * Initialize database connection and create tables
   */
  async init(): Promise<void> {
    try {
      // Ensure data directory exists
      const dbDir = plugins.path.dirname(this.dbPath);
      await Deno.mkdir(dbDir, { recursive: true });

      // Open database
      this.db = new plugins.sqlite.DB(this.dbPath);
      logger.info(`Database initialized at ${this.dbPath}`);

      // Create tables
      await this.createTables();

      // Run migrations if needed
      const runner = new MigrationRunner(this.query.bind(this));
      runner.run();

      // Initialize repositories with bound query function
      const queryFn = this.query.bind(this);
      this.serviceRepo = new ServiceRepository(queryFn);
      this.registryRepo = new RegistryRepository(queryFn);
      this.certificateRepo = new CertificateRepository(queryFn);
      this.authRepo = new AuthRepository(queryFn);
      this.metricsRepo = new MetricsRepository(queryFn);
      this.platformRepo = new PlatformRepository(queryFn);
      this.backupRepo = new BackupRepository(queryFn);
      this.secretSettings = new SecretSettingsManager(this.authRepo);
    } catch (error) {
      logger.error(`Failed to initialize database: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Create all database tables
   */
  private async createTables(): Promise<void> {
    if (!this.db) throw new Error('Database not initialized');

    // Services table
    this.query(`
      CREATE TABLE IF NOT EXISTS services (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL UNIQUE,
        image TEXT NOT NULL,
        registry TEXT,
        env_vars TEXT NOT NULL,
        port INTEGER NOT NULL,
        domain TEXT,
        container_id TEXT,
        status TEXT NOT NULL DEFAULT 'stopped',
        created_at INTEGER NOT NULL,
        updated_at INTEGER NOT NULL
      )
    `);

    this.query(`
      CREATE TABLE IF NOT EXISTS hosted_app_lifecycle (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        service_id INTEGER NOT NULL UNIQUE,
        app_instance_id TEXT NOT NULL UNIQUE,
        control_token TEXT NOT NULL,
        host_type TEXT NOT NULL,
        state_json TEXT NOT NULL,
        created_at INTEGER NOT NULL,
        updated_at INTEGER NOT NULL,
        FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
      )
    `);

    // Registries table
    this.query(`
      CREATE TABLE IF NOT EXISTS registries (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        url TEXT NOT NULL UNIQUE,
        username TEXT NOT NULL,
        password_encrypted TEXT NOT NULL,
        created_at INTEGER NOT NULL
      )
    `);

    // Nginx configs table
    this.query(`
      CREATE TABLE IF NOT EXISTS nginx_configs (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        service_id INTEGER NOT NULL,
        domain TEXT NOT NULL,
        port INTEGER NOT NULL,
        ssl_enabled INTEGER NOT NULL DEFAULT 0,
        config_template TEXT NOT NULL,
        created_at INTEGER NOT NULL,
        updated_at INTEGER NOT NULL,
        FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
      )
    `);

    // SSL certificates table
    this.query(`
      CREATE TABLE IF NOT EXISTS ssl_certificates (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        domain TEXT NOT NULL UNIQUE,
        cert_path TEXT NOT NULL,
        key_path TEXT NOT NULL,
        full_chain_path TEXT NOT NULL,
        expiry_date INTEGER NOT NULL,
        issuer TEXT NOT NULL,
        created_at INTEGER NOT NULL,
        updated_at INTEGER NOT NULL
      )
    `);

    // DNS records table
    this.query(`
      CREATE TABLE IF NOT EXISTS dns_records (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        domain TEXT NOT NULL UNIQUE,
        type TEXT NOT NULL,
        value TEXT NOT NULL,
        cloudflare_id TEXT,
        zone_id TEXT,
        created_at INTEGER NOT NULL,
        updated_at INTEGER NOT NULL
      )
    `);

    // Metrics table
    this.query(`
      CREATE TABLE IF NOT EXISTS metrics (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        service_id INTEGER NOT NULL,
        timestamp INTEGER NOT NULL,
        cpu_percent REAL NOT NULL,
        memory_used INTEGER NOT NULL,
        memory_limit INTEGER NOT NULL,
        network_rx_bytes INTEGER NOT NULL,
        network_tx_bytes INTEGER NOT NULL,
        FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
      )
    `);

    // Create index for metrics queries
    this.query(`
      CREATE INDEX IF NOT EXISTS idx_metrics_service_timestamp
      ON metrics(service_id, timestamp DESC)
    `);

    // Logs table
    this.query(`
      CREATE TABLE IF NOT EXISTS logs (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        service_id INTEGER NOT NULL,
        timestamp INTEGER NOT NULL,
        message TEXT NOT NULL,
        level TEXT NOT NULL,
        source TEXT NOT NULL,
        FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
      )
    `);

    // Create index for logs queries
    this.query(`
      CREATE INDEX IF NOT EXISTS idx_logs_service_timestamp
      ON logs(service_id, timestamp DESC)
    `);

    // Users table
    this.query(`
      CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT NOT NULL UNIQUE,
        password_hash TEXT NOT NULL,
        role TEXT NOT NULL DEFAULT 'user',
        created_at INTEGER NOT NULL,
        updated_at INTEGER NOT NULL
      )
    `);

    // Settings table
    this.query(`
      CREATE TABLE IF NOT EXISTS settings (
        key TEXT PRIMARY KEY,
        value TEXT NOT NULL,
        updated_at INTEGER NOT NULL
      )
    `);

    this.query(`
      CREATE TABLE IF NOT EXISTS secret_settings (
        key TEXT PRIMARY KEY,
        value TEXT NOT NULL,
        updated_at INTEGER NOT NULL
      )
    `);

    // Version table for migrations
    this.query(`
      CREATE TABLE IF NOT EXISTS migrations (
        version INTEGER PRIMARY KEY,
        applied_at INTEGER NOT NULL
      )
    `);

    logger.debug('Database tables created successfully');
  }

  /**
   * Run database migrations
   */
  /**
   * Close database connection
   */
  close(): void {
    if (this.db) {
      this.db.close();
      this.db = null;
      logger.debug('Database connection closed');
    }
  }

  /**
   * Execute a raw query
   */
  query<T = Record<string, unknown>>(sql: string, params: TBindValue[] = []): T[] {
    if (!this.db) {
      const error = new Error('Database not initialized');
      console.error('Database access before initialization!');
      console.error('Stack trace:', error.stack);
      throw error;
    }

    if (params.length === 0 && !sql.trim().toUpperCase().startsWith('SELECT')) {
      this.db.exec(sql);
      return [] as T[];
    }

    const stmt = this.db.prepare(sql);
    try {
      const result = stmt.all(...params);
      return result as T[];
    } finally {
      stmt.finalize();
    }
  }

  // ============ Services CRUD (delegated to repository) ============

  async createService(service: Omit<IService, 'id'>): Promise<IService> {
    return this.serviceRepo.create(service);
  }

  getServiceByName(name: string): IService | null {
    return this.serviceRepo.getByName(name);
  }

  getServiceByID(id: number): IService | null {
    return this.serviceRepo.getById(id);
  }

  getAllServices(): IService[] {
    return this.serviceRepo.getAll();
  }

  updateService(id: number, updates: Partial<IService>): void {
    this.serviceRepo.update(id, updates);
  }

  deleteService(id: number): void {
    this.serviceRepo.delete(id);
  }

  // ============ Hosted App Lifecycle ============

  upsertHostedAppLifecycle(recordArg: Omit<IHostedAppLifecycleRecord, 'id' | 'createdAt' | 'updatedAt'>): IHostedAppLifecycleRecord {
    const existing = this.getHostedAppLifecycleByServiceId(recordArg.serviceId);
    const now = Date.now();
    if (existing) {
      this.query(
        `UPDATE hosted_app_lifecycle
          SET app_instance_id = ?, control_token = ?, host_type = ?, state_json = ?, updated_at = ?
          WHERE service_id = ?`,
        [
          recordArg.appInstanceId,
          recordArg.controlToken,
          recordArg.hostType,
          JSON.stringify(recordArg.state),
          now,
          recordArg.serviceId,
        ],
      );
    } else {
      this.query(
        `INSERT INTO hosted_app_lifecycle (
          service_id, app_instance_id, control_token, host_type, state_json, created_at, updated_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?)`,
        [
          recordArg.serviceId,
          recordArg.appInstanceId,
          recordArg.controlToken,
          recordArg.hostType,
          JSON.stringify(recordArg.state),
          now,
          now,
        ],
      );
    }
    return this.getHostedAppLifecycleByServiceId(recordArg.serviceId)!;
  }

  getHostedAppLifecycleByServiceId(serviceIdArg: number): IHostedAppLifecycleRecord | null {
    const rows = this.query('SELECT * FROM hosted_app_lifecycle WHERE service_id = ?', [serviceIdArg]);
    return rows[0] ? this.rowToHostedAppLifecycle(rows[0]) : null;
  }

  getHostedAppLifecycleByInstanceId(appInstanceIdArg: string): IHostedAppLifecycleRecord | null {
    const rows = this.query('SELECT * FROM hosted_app_lifecycle WHERE app_instance_id = ?', [appInstanceIdArg]);
    return rows[0] ? this.rowToHostedAppLifecycle(rows[0]) : null;
  }

  updateHostedAppLifecycleState(serviceIdArg: number, stateArg: IHostedAppLifecycleState): IHostedAppLifecycleRecord {
    this.query(
      'UPDATE hosted_app_lifecycle SET state_json = ?, updated_at = ? WHERE service_id = ?',
      [JSON.stringify(stateArg), Date.now(), serviceIdArg],
    );
    return this.getHostedAppLifecycleByServiceId(serviceIdArg)!;
  }

  private rowToHostedAppLifecycle(rowArg: Record<string, unknown>): IHostedAppLifecycleRecord {
    let state: IHostedAppLifecycleState;
    try {
      state = JSON.parse(String(rowArg.state_json || '{}'));
    } catch {
      state = {
        appInstanceId: String(rowArg.app_instance_id),
        hostType: String(rowArg.host_type),
        runtimeStatus: 'unknown',
      };
    }
    return {
      id: Number(rowArg.id),
      serviceId: Number(rowArg.service_id),
      appInstanceId: String(rowArg.app_instance_id),
      controlToken: String(rowArg.control_token),
      hostType: 'onebox',
      state,
      createdAt: Number(rowArg.created_at),
      updatedAt: Number(rowArg.updated_at),
    };
  }

  // ============ Registries CRUD (delegated to repository) ============

  async createRegistry(registry: Omit<IRegistry, 'id'>): Promise<IRegistry> {
    return this.registryRepo.createRegistry(registry);
  }

  getRegistryByURL(url: string): IRegistry | null {
    return this.registryRepo.getRegistryByURL(url);
  }

  getAllRegistries(): IRegistry[] {
    return this.registryRepo.getAllRegistries();
  }

  deleteRegistry(url: string): void {
    this.registryRepo.deleteRegistry(url);
  }

  // ============ Settings CRUD (delegated to repository) ============

  getSetting(key: string): string | null {
    return this.authRepo.getSetting(key);
  }

  setSetting(key: string, value: string): void {
    this.authRepo.setSetting(key, value);
  }

  deleteSetting(key: string): void {
    this.authRepo.deleteSetting(key);
  }

  getAllSettings(): Record<string, string> {
    return this.authRepo.getAllSettings();
  }

  async getSecretSetting(key: string): Promise<string | null> {
    return await this.secretSettings.get(key);
  }

  async setSecretSetting(key: string, value: string | null): Promise<void> {
    await this.secretSettings.set(key, value);
  }

  async hasSecretSetting(key: string): Promise<boolean> {
    return await this.secretSettings.has(key);
  }

  isSecretSettingKey(key: string): boolean {
    return this.secretSettings.isSecretKey(key);
  }

  getCanonicalSecretSettingKeys(): string[] {
    return this.secretSettings.getCanonicalKeys();
  }

  // ============ Users CRUD (delegated to repository) ============

  async createUser(user: Omit<IUser, 'id'>): Promise<IUser> {
    return this.authRepo.createUser(user);
  }

  getUserByUsername(username: string): IUser | null {
    return this.authRepo.getUserByUsername(username);
  }

  getAllUsers(): IUser[] {
    return this.authRepo.getAllUsers();
  }

  updateUserPassword(username: string, passwordHash: string): void {
    this.authRepo.updateUserPassword(username, passwordHash);
  }

  deleteUser(username: string): void {
    this.authRepo.deleteUser(username);
  }

  // ============ Metrics (delegated to repository) ============

  addMetric(metric: Omit<IMetric, 'id'>): void {
    this.metricsRepo.addMetric(metric);
  }

  getMetrics(serviceId: number, limit = 100): IMetric[] {
    return this.metricsRepo.getMetrics(serviceId, limit);
  }

  // ============ Logs (delegated to repository) ============

  addLog(log: Omit<ILogEntry, 'id'>): void {
    this.metricsRepo.addLog(log);
  }

  getLogs(serviceId: number, limit = 1000): ILogEntry[] {
    return this.metricsRepo.getLogs(serviceId, limit);
  }

  // ============ SSL Certificates Legacy API (delegated to repository) ============

  async createSSLCertificate(cert: Omit<ISslCertificate, 'id'>): Promise<ISslCertificate> {
    return this.certificateRepo.createSSLCertificate(cert);
  }

  getSSLCertificate(domain: string): ISslCertificate | null {
    return this.certificateRepo.getSSLCertificate(domain);
  }

  getAllSSLCertificates(): ISslCertificate[] {
    return this.certificateRepo.getAllSSLCertificates();
  }

  updateSSLCertificate(domain: string, updates: Partial<ISslCertificate>): void {
    this.certificateRepo.updateSSLCertificate(domain, updates);
  }

  deleteSSLCertificate(domain: string): void {
    this.certificateRepo.deleteSSLCertificate(domain);
  }

  // ============ Domains (delegated to repository) ============

  createDomain(domain: Omit<IDomain, 'id'>): IDomain {
    return this.certificateRepo.createDomain(domain);
  }

  getDomainByName(domain: string): IDomain | null {
    return this.certificateRepo.getDomainByName(domain);
  }

  getDomainById(id: number): IDomain | null {
    return this.certificateRepo.getDomainById(id);
  }

  getAllDomains(): IDomain[] {
    return this.certificateRepo.getAllDomains();
  }

  getDomainsByProvider(provider: NonNullable<IDomain['dnsProvider']>): IDomain[] {
    return this.certificateRepo.getDomainsByProvider(provider);
  }

  updateDomain(id: number, updates: Partial<IDomain>): void {
    this.certificateRepo.updateDomain(id, updates);
  }

  deleteDomain(id: number): void {
    this.certificateRepo.deleteDomain(id);
  }

  // ============ Certificates (delegated to repository) ============

  createCertificate(cert: Omit<ICertificate, 'id'>): ICertificate {
    return this.certificateRepo.createCertificate(cert);
  }

  getCertificateById(id: number): ICertificate | null {
    return this.certificateRepo.getCertificateById(id);
  }

  getCertificatesByDomain(domainId: number): ICertificate[] {
    return this.certificateRepo.getCertificatesByDomain(domainId);
  }

  getAllCertificates(): ICertificate[] {
    return this.certificateRepo.getAllCertificates();
  }

  updateCertificate(id: number, updates: Partial<ICertificate>): void {
    this.certificateRepo.updateCertificate(id, updates);
  }

  deleteCertificate(id: number): void {
    this.certificateRepo.deleteCertificate(id);
  }

  // ============ Certificate Requirements (delegated to repository) ============

  createCertRequirement(req: Omit<ICertRequirement, 'id'>): ICertRequirement {
    return this.certificateRepo.createCertRequirement(req);
  }

  getCertRequirementById(id: number): ICertRequirement | null {
    return this.certificateRepo.getCertRequirementById(id);
  }

  getCertRequirementsByService(serviceId: number): ICertRequirement[] {
    return this.certificateRepo.getCertRequirementsByService(serviceId);
  }

  getCertRequirementsByDomain(domainId: number): ICertRequirement[] {
    return this.certificateRepo.getCertRequirementsByDomain(domainId);
  }

  getAllCertRequirements(): ICertRequirement[] {
    return this.certificateRepo.getAllCertRequirements();
  }

  updateCertRequirement(id: number, updates: Partial<ICertRequirement>): void {
    this.certificateRepo.updateCertRequirement(id, updates);
  }

  deleteCertRequirement(id: number): void {
    this.certificateRepo.deleteCertRequirement(id);
  }

  // ============ Registry Tokens (delegated to repository) ============

  createRegistryToken(token: Omit<IRegistryToken, 'id'>): IRegistryToken {
    return this.registryRepo.createToken(token);
  }

  getRegistryTokenById(id: number): IRegistryToken | null {
    return this.registryRepo.getTokenById(id);
  }

  getRegistryTokenByHash(tokenHash: string): IRegistryToken | null {
    return this.registryRepo.getTokenByHash(tokenHash);
  }

  getAllRegistryTokens(): IRegistryToken[] {
    return this.registryRepo.getAllTokens();
  }

  getRegistryTokensByType(type: 'global' | 'ci'): IRegistryToken[] {
    return this.registryRepo.getTokensByType(type);
  }

  updateRegistryTokenLastUsed(id: number): void {
    this.registryRepo.updateTokenLastUsed(id);
  }

  deleteRegistryToken(id: number): void {
    this.registryRepo.deleteToken(id);
  }

  // ============ Platform Services (delegated to repository) ============

  createPlatformService(service: Omit<IPlatformService, 'id'>): IPlatformService {
    return this.platformRepo.createPlatformService(service);
  }

  getPlatformServiceByName(name: string): IPlatformService | null {
    return this.platformRepo.getPlatformServiceByName(name);
  }

  getPlatformServiceById(id: number): IPlatformService | null {
    return this.platformRepo.getPlatformServiceById(id);
  }

  getPlatformServiceByType(type: TPlatformServiceType): IPlatformService | null {
    return this.platformRepo.getPlatformServiceByType(type);
  }

  getAllPlatformServices(): IPlatformService[] {
    return this.platformRepo.getAllPlatformServices();
  }

  updatePlatformService(id: number, updates: Partial<IPlatformService>): void {
    this.platformRepo.updatePlatformService(id, updates);
  }

  deletePlatformService(id: number): void {
    this.platformRepo.deletePlatformService(id);
  }

  // ============ Platform Resources (delegated to repository) ============

  createPlatformResource(resource: Omit<IPlatformResource, 'id'>): IPlatformResource {
    return this.platformRepo.createPlatformResource(resource);
  }

  getPlatformResourceById(id: number): IPlatformResource | null {
    return this.platformRepo.getPlatformResourceById(id);
  }

  getPlatformResourcesByService(serviceId: number): IPlatformResource[] {
    return this.platformRepo.getPlatformResourcesByService(serviceId);
  }

  getPlatformResourcesByPlatformService(platformServiceId: number): IPlatformResource[] {
    return this.platformRepo.getPlatformResourcesByPlatformService(platformServiceId);
  }

  getAllPlatformResources(): IPlatformResource[] {
    return this.platformRepo.getAllPlatformResources();
  }

  deletePlatformResource(id: number): void {
    this.platformRepo.deletePlatformResource(id);
  }

  deletePlatformResourcesByService(serviceId: number): void {
    this.platformRepo.deletePlatformResourcesByService(serviceId);
  }

  // ============ Backups (delegated to repository) ============

  createBackup(backup: Omit<IBackup, 'id'>): IBackup {
    return this.backupRepo.create(backup);
  }

  getBackupById(id: number): IBackup | null {
    return this.backupRepo.getById(id);
  }

  getBackupsByService(serviceId: number): IBackup[] {
    return this.backupRepo.getByService(serviceId);
  }

  getAllBackups(): IBackup[] {
    return this.backupRepo.getAll();
  }

  deleteBackup(id: number): void {
    this.backupRepo.delete(id);
  }

  deleteBackupsByService(serviceId: number): void {
    this.backupRepo.deleteByService(serviceId);
  }

  getBackupsBySchedule(scheduleId: number): IBackup[] {
    return this.backupRepo.getBySchedule(scheduleId);
  }

  getBackupBySnapshotId(snapshotId: string): IBackup | null {
    return this.backupRepo.getBySnapshotId(snapshotId);
  }

  // ============ Backup Schedules (delegated to repository) ============

  createBackupSchedule(schedule: Omit<IBackupSchedule, 'id'>): IBackupSchedule {
    return this.backupRepo.createSchedule(schedule);
  }

  getBackupScheduleById(id: number): IBackupSchedule | null {
    return this.backupRepo.getScheduleById(id);
  }

  getBackupSchedulesByService(serviceId: number): IBackupSchedule[] {
    return this.backupRepo.getSchedulesByService(serviceId);
  }

  getEnabledBackupSchedules(): IBackupSchedule[] {
    return this.backupRepo.getEnabledSchedules();
  }

  getAllBackupSchedules(): IBackupSchedule[] {
    return this.backupRepo.getAllSchedules();
  }

  updateBackupSchedule(id: number, updates: IBackupScheduleUpdate & { lastRunAt?: number; nextRunAt?: number; lastStatus?: 'success' | 'failed' | null; lastError?: string | null }): void {
    this.backupRepo.updateSchedule(id, updates);
  }

  deleteBackupSchedule(id: number): void {
    this.backupRepo.deleteSchedule(id);
  }

  deleteBackupSchedulesByService(serviceId: number): void {
    this.backupRepo.deleteSchedulesByService(serviceId);
  }
}
