import type Database from 'better-sqlite3';

export interface BrowserSessionProfile {
  id: number;
  ownerId: string;
  label: string;
  startUrl: string;
  matchPatterns: string[];
  storageOrigins: string[];
  loggedInSelector: string | null;
  loginUrlPatterns: string[];
  encryptedStateBlob: Buffer | null;
  stateVersion: number;
  playwrightVersion: string | null;
  status: 'pending' | 'active' | 'expired' | 'revoked' | 'error';
  lastSavedAt: string | null;
  lastUsedAt: string | null;
  lastValidatedAt: string | null;
  lastError: string | null;
  createdAt: string;
  updatedAt: string;
}

export interface CreateProfileInput {
  ownerId: string;
  label: string;
  startUrl: string;
  matchPatterns: string[];
  storageOrigins: string[];
  loggedInSelector?: string | null;
  loginUrlPatterns: string[];
}

export interface AuditInput {
  actorUserId?: string | null;
  ownerId?: string | null;
  profileId?: number | null;
  action:
    | 'create' | 'save' | 'decrypt' | 'use' | 'delete'
    | 'expire' | 'revoke' | 'test' | 'login_start' | 'login_cancel';
  taskId?: number | null;
  jobId?: string | null;
  result: 'success' | 'error';
  reason?: string | null;
}

function rowToProfile(row: Record<string, unknown>): BrowserSessionProfile {
  return {
    id: row['id'] as number,
    ownerId: row['owner_id'] as string,
    label: row['label'] as string,
    startUrl: row['start_url'] as string,
    matchPatterns: JSON.parse((row['match_patterns'] as string) || '[]') as string[],
    storageOrigins: JSON.parse((row['storage_origins'] as string) || '[]') as string[],
    loggedInSelector: (row['logged_in_selector'] as string | null) ?? null,
    loginUrlPatterns: JSON.parse((row['login_url_patterns'] as string) || '[]') as string[],
    encryptedStateBlob: (row['encrypted_state_blob'] as Buffer | null) ?? null,
    stateVersion: (row['state_version'] as number) ?? 0,
    playwrightVersion: (row['playwright_version'] as string | null) ?? null,
    status: row['status'] as BrowserSessionProfile['status'],
    lastSavedAt: (row['last_saved_at'] as string | null) ?? null,
    lastUsedAt: (row['last_used_at'] as string | null) ?? null,
    lastValidatedAt: (row['last_validated_at'] as string | null) ?? null,
    lastError: (row['last_error'] as string | null) ?? null,
    createdAt: row['created_at'] as string,
    updatedAt: row['updated_at'] as string,
  };
}

export class BrowserSessionRepo {
  constructor(private readonly db: Database.Database) {}

  // ── DEK management ────────────────────────────────────────────────
  setUserDek(userId: string, encryptedDek: Buffer): void {
    this.db.prepare(`
      INSERT INTO user_deks (user_id, encrypted_dek)
      VALUES (?, ?)
      ON CONFLICT(user_id) DO UPDATE SET encrypted_dek = excluded.encrypted_dek
    `).run(userId, encryptedDek);
  }

  getUserDek(userId: string): Buffer | null {
    const row = this.db.prepare('SELECT encrypted_dek FROM user_deks WHERE user_id = ?').get(userId) as { encrypted_dek: Buffer } | undefined;
    return row?.encrypted_dek ?? null;
  }

  // ── Profiles ──────────────────────────────────────────────────────
  createProfile(input: CreateProfileInput): number {
    const result = this.db.prepare(`
      INSERT INTO browser_session_profiles
        (owner_id, label, start_url, match_patterns, storage_origins,
         logged_in_selector, login_url_patterns, status)
      VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')
    `).run(
      input.ownerId,
      input.label,
      input.startUrl,
      JSON.stringify(input.matchPatterns),
      JSON.stringify(input.storageOrigins),
      input.loggedInSelector ?? null,
      JSON.stringify(input.loginUrlPatterns),
    );
    return Number(result.lastInsertRowid);
  }

  getProfileById(id: number, ownerId: string): BrowserSessionProfile | null {
    const row = this.db.prepare('SELECT * FROM browser_session_profiles WHERE id = ? AND owner_id = ?').get(id, ownerId) as Record<string, unknown> | undefined;
    return row ? rowToProfile(row) : null;
  }

  /** Admin / worker path that does NOT enforce ownership. Caller must check elsewhere. */
  getProfileByIdUnsafe(id: number): BrowserSessionProfile | null {
    const row = this.db.prepare('SELECT * FROM browser_session_profiles WHERE id = ?').get(id) as Record<string, unknown> | undefined;
    return row ? rowToProfile(row) : null;
  }

  listProfilesByOwner(ownerId: string): BrowserSessionProfile[] {
    const rows = this.db.prepare('SELECT * FROM browser_session_profiles WHERE owner_id = ? ORDER BY label ASC').all(ownerId) as Array<Record<string, unknown>>;
    return rows.map(rowToProfile);
  }

  saveProfileBlob(id: number, encrypted: Buffer, playwrightVersion: string): void {
    this.db.prepare(`
      UPDATE browser_session_profiles
      SET encrypted_state_blob = ?,
          state_version = state_version + 1,
          playwright_version = ?,
          status = 'active',
          last_saved_at = datetime('now'),
          last_validated_at = datetime('now'),
          last_error = NULL,
          updated_at = datetime('now')
      WHERE id = ?
    `).run(encrypted, playwrightVersion, id);
  }

  markProfileStatus(id: number, status: BrowserSessionProfile['status'], reason: string | null = null): void {
    this.db.prepare(`
      UPDATE browser_session_profiles
      SET status = ?,
          last_error = ?,
          updated_at = datetime('now')
      WHERE id = ?
    `).run(status, reason, id);
  }

  touchUsed(id: number): void {
    this.db.prepare(`UPDATE browser_session_profiles SET last_used_at = datetime('now') WHERE id = ?`).run(id);
  }

  deleteProfile(id: number, ownerId: string): boolean {
    const result = this.db.prepare('DELETE FROM browser_session_profiles WHERE id = ? AND owner_id = ?').run(id, ownerId);
    return result.changes > 0;
  }

  // ── Audit ─────────────────────────────────────────────────────────
  audit(input: AuditInput): void {
    this.db.prepare(`
      INSERT INTO browser_session_audit
        (actor_user_id, profile_id, owner_id, action, task_id, job_id, result, reason)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    `).run(
      input.actorUserId ?? null,
      input.profileId ?? null,
      input.ownerId ?? null,
      input.action,
      input.taskId ?? null,
      input.jobId ?? null,
      input.result,
      input.reason ?? null,
    );
  }
}
