// Extracted from src/db/repository.ts (mechanical split — bodies unchanged).
// notifications repository domain. Facade: src/db/repository.ts (Repository class).
import Database from 'better-sqlite3';
import { randomUUID } from 'crypto';

// ── Browser Notifications V2 (Web Push) ──────────────────────────────
// Spec: docs/superpowers/specs/2026-05-28-browser-notifications-v2-webpush.md

export type NotifyEventType = 'running' | 'succeeded' | 'failed' | 'waiting_human';


export interface PushSubscriptionRecord {
  id: string;
  userId: string;
  endpoint: string;
  p256dh: string;
  auth: string;
  userAgent: string | null;
  vapidKeyId: string;
  createdAt: string;
  lastSuccessAt: string | null;
  lastFailureAt: string | null;
  failureCount: number;
}


export interface UpsertPushSubscriptionInput {
  userId: string;
  endpoint: string;
  p256dh: string;
  auth: string;
  userAgent?: string | null;
  vapidKeyId: string;
}


export interface NotificationPrefs {
  userId: string;
  enabled: boolean;
  events: Record<NotifyEventType, boolean>;
  includeDetails: boolean;
  v1Migrated: boolean;
  updatedAt: string;
}


export type NotificationPrefsUpdate = Partial<Omit<NotificationPrefs, 'userId' | 'updatedAt'>>;


  // ── Browser Notifications V2: push_subscriptions ────────────────────
  // Spec: docs/superpowers/specs/2026-05-28-browser-notifications-v2-webpush.md

  /**
   * Insert or, on endpoint collision, transfer ownership to the new user.
   * endpoint is globally UNIQUE: the same browser logging in as a different
   * user re-uses the same push service URL, so we move it rather than fail.
   */
  export function upsertPushSubscription(db: Database.Database, input: UpsertPushSubscriptionInput): { id: string } {
    const existing = db
      .prepare('SELECT id FROM push_subscriptions WHERE endpoint = ?')
      .get(input.endpoint) as { id: string } | undefined;
    if (existing) {
      db
        .prepare(
          `UPDATE push_subscriptions
              SET user_id = ?, p256dh = ?, auth = ?, user_agent = ?,
                  vapid_key_id = ?, last_success_at = NULL,
                  last_failure_at = NULL, failure_count = 0
            WHERE id = ?`,
        )
        .run(
          input.userId,
          input.p256dh,
          input.auth,
          input.userAgent ?? null,
          input.vapidKeyId,
          existing.id,
        );
      return { id: existing.id };
    }
    const id = randomUUID();
    db
      .prepare(
        `INSERT INTO push_subscriptions
           (id, user_id, endpoint, p256dh, auth, user_agent, vapid_key_id)
         VALUES (?, ?, ?, ?, ?, ?, ?)`,
      )
      .run(
        id,
        input.userId,
        input.endpoint,
        input.p256dh,
        input.auth,
        input.userAgent ?? null,
        input.vapidKeyId,
      );
    return { id };
  }


  export function listPushSubscriptionsForUser(db: Database.Database, userId: string): PushSubscriptionRecord[] {
    const rows = db
      .prepare(
        `SELECT id, user_id, endpoint, p256dh, auth, user_agent, vapid_key_id,
                created_at, last_success_at, last_failure_at, failure_count
           FROM push_subscriptions
          WHERE user_id = ?
          ORDER BY created_at ASC`,
      )
      .all(userId) as Array<{
      id: string;
      user_id: string;
      endpoint: string;
      p256dh: string;
      auth: string;
      user_agent: string | null;
      vapid_key_id: string;
      created_at: string;
      last_success_at: string | null;
      last_failure_at: string | null;
      failure_count: number;
    }>;
    return rows.map(rowToPushSubscription);
  }


  export function getPushSubscriptionById(db: Database.Database, id: string): PushSubscriptionRecord | null {
    const row = db
      .prepare(
        `SELECT id, user_id, endpoint, p256dh, auth, user_agent, vapid_key_id,
                created_at, last_success_at, last_failure_at, failure_count
           FROM push_subscriptions WHERE id = ?`,
      )
      .get(id) as Parameters<typeof rowToPushSubscription>[0] | undefined;
    return row ? rowToPushSubscription(row) : null;
  }


  export function deletePushSubscription(db: Database.Database, id: string): void {
    db.prepare('DELETE FROM push_subscriptions WHERE id = ?').run(id);
  }


  export function markPushSubscriptionSuccess(db: Database.Database, id: string): void {
    db
      .prepare(
        `UPDATE push_subscriptions
            SET last_success_at = datetime('now'), failure_count = 0
          WHERE id = ?`,
      )
      .run(id);
  }


  export function markPushSubscriptionFailure(db: Database.Database, id: string): void {
    db
      .prepare(
        `UPDATE push_subscriptions
            SET last_failure_at = datetime('now'),
                failure_count = failure_count + 1
          WHERE id = ?`,
      )
      .run(id);
  }


  // ── Browser Notifications V2: user_notification_prefs ────────────────

  /**
   * Read per-user prefs; create row with defaults if none exists.
   * Defaults: enabled=true, all events on, include_details=false, v1_migrated=false.
   */
  export function getUserNotificationPrefs(db: Database.Database, userId: string): NotificationPrefs {
    const row = db
      .prepare(
        `SELECT user_id, enabled, event_running, event_succeeded,
                event_failed, event_waiting_human, include_details,
                v1_migrated, updated_at
           FROM user_notification_prefs WHERE user_id = ?`,
      )
      .get(userId) as
      | {
          user_id: string;
          enabled: number;
          event_running: number;
          event_succeeded: number;
          event_failed: number;
          event_waiting_human: number;
          include_details: number;
          v1_migrated: number;
          updated_at: string;
        }
      | undefined;
    if (!row) {
      // Lazily create the default row so subsequent reads/updates are
      // a simple UPDATE rather than a conditional insert.
      db
        .prepare(
          `INSERT INTO user_notification_prefs (user_id) VALUES (?)
           ON CONFLICT(user_id) DO NOTHING`,
        )
        .run(userId);
      return {
        userId,
        enabled: true,
        events: { running: true, succeeded: true, failed: true, waiting_human: true },
        includeDetails: false,
        v1Migrated: false,
        updatedAt: new Date().toISOString(),
      };
    }
    return {
      userId: row.user_id,
      enabled: row.enabled !== 0,
      events: {
        running: row.event_running !== 0,
        succeeded: row.event_succeeded !== 0,
        failed: row.event_failed !== 0,
        waiting_human: row.event_waiting_human !== 0,
      },
      includeDetails: row.include_details !== 0,
      v1Migrated: row.v1_migrated !== 0,
      updatedAt: row.updated_at,
    };
  }


  export function upsertUserNotificationPrefs(db: Database.Database, userId: string, update: NotificationPrefsUpdate): void {
    // Ensure a row exists first (lazy default creation matches getUserNotificationPrefs).
    db
      .prepare(`INSERT OR IGNORE INTO user_notification_prefs (user_id) VALUES (?)`)
      .run(userId);
    const sets: string[] = [];
    const params: Array<string | number> = [];
    if (update.enabled !== undefined) {
      sets.push('enabled = ?');
      params.push(update.enabled ? 1 : 0);
    }
    if (update.events) {
      if (update.events.running !== undefined) {
        sets.push('event_running = ?');
        params.push(update.events.running ? 1 : 0);
      }
      if (update.events.succeeded !== undefined) {
        sets.push('event_succeeded = ?');
        params.push(update.events.succeeded ? 1 : 0);
      }
      if (update.events.failed !== undefined) {
        sets.push('event_failed = ?');
        params.push(update.events.failed ? 1 : 0);
      }
      if (update.events.waiting_human !== undefined) {
        sets.push('event_waiting_human = ?');
        params.push(update.events.waiting_human ? 1 : 0);
      }
    }
    if (update.includeDetails !== undefined) {
      sets.push('include_details = ?');
      params.push(update.includeDetails ? 1 : 0);
    }
    if (update.v1Migrated !== undefined) {
      sets.push('v1_migrated = ?');
      params.push(update.v1Migrated ? 1 : 0);
    }
    if (sets.length === 0) return;
    sets.push("updated_at = datetime('now')");
    params.push(userId);
    db
      .prepare(`UPDATE user_notification_prefs SET ${sets.join(', ')} WHERE user_id = ?`)
      .run(...params);
  }


  /**
   * One-time V1 (localStorage) → V2 (server) preferences migration marker.
   * Returns true if this call performed the migration mark (caller should
   * then apply the localStorage values via upsertUserNotificationPrefs).
   * Returns false if already migrated (caller should treat as 409 conflict).
   */
  export function markV1MigrationComplete(db: Database.Database, userId: string): boolean {
    db
      .prepare(`INSERT OR IGNORE INTO user_notification_prefs (user_id) VALUES (?)`)
      .run(userId);
    const result = db
      .prepare(
        `UPDATE user_notification_prefs
            SET v1_migrated = 1, updated_at = datetime('now')
          WHERE user_id = ? AND v1_migrated = 0`,
      )
      .run(userId);
    return result.changes > 0;
  }


export function rowToPushSubscription(row: {
  id: string;
  user_id: string;
  endpoint: string;
  p256dh: string;
  auth: string;
  user_agent: string | null;
  vapid_key_id: string;
  created_at: string;
  last_success_at: string | null;
  last_failure_at: string | null;
  failure_count: number;
}): PushSubscriptionRecord {
  return {
    id: row.id,
    userId: row.user_id,
    endpoint: row.endpoint,
    p256dh: row.p256dh,
    auth: row.auth,
    userAgent: row.user_agent,
    vapidKeyId: row.vapid_key_id,
    createdAt: row.created_at,
    lastSuccessAt: row.last_success_at,
    lastFailureAt: row.last_failure_at,
    failureCount: row.failure_count,
  };
}
