import { Inject, Injectable, Logger } from '@nestjs/common';
import { eq, and, sql, type Db, preferences as preferencesTable } from '@mosaicstack/db';
import { DB } from '../database/database.module.js';

export const PLATFORM_DEFAULTS: Record<string, unknown> = {
  'agent.defaultModel': null,
  'agent.thinkingLevel': 'auto',
  'agent.streamingEnabled': true,
  'response.language': 'auto',
  'response.codeAnnotations': true,
  'safety.confirmDestructiveTools': true,
  'session.autoCompactThreshold': 0.8,
  'session.autoCompactEnabled': true,
  'limits.maxThinkingLevel': null,
  'limits.rateLimit': null,
};

export const IMMUTABLE_KEYS = new Set<string>(['limits.maxThinkingLevel', 'limits.rateLimit']);

@Injectable()
export class PreferencesService {
  private readonly logger = new Logger(PreferencesService.name);

  constructor(@Inject(DB) private readonly db: Db) {}

  /**
   * Returns the effective preference set for a user:
   * Platform defaults → user overrides (mutable keys only) → enforcements re-applied last
   */
  async getEffective(userId: string): Promise<Record<string, unknown>> {
    const userPrefs = await this.getUserPrefs(userId);
    const result: Record<string, unknown> = { ...PLATFORM_DEFAULTS };

    for (const [key, value] of Object.entries(userPrefs)) {
      if (!IMMUTABLE_KEYS.has(key)) {
        result[key] = value;
      }
    }

    // Re-apply immutable keys (enforcements always win)
    for (const key of IMMUTABLE_KEYS) {
      result[key] = PLATFORM_DEFAULTS[key];
    }

    return result;
  }

  async set(
    userId: string,
    key: string,
    value: unknown,
  ): Promise<{ success: boolean; message: string }> {
    if (IMMUTABLE_KEYS.has(key)) {
      return {
        success: false,
        message: `Cannot override "${key}" — this is a platform enforcement. Contact your admin.`,
      };
    }

    await this.upsertPref(userId, key, value);
    return { success: true, message: `Preference "${key}" set to ${JSON.stringify(value)}.` };
  }

  async reset(userId: string, key: string): Promise<{ success: boolean; message: string }> {
    if (IMMUTABLE_KEYS.has(key)) {
      return { success: false, message: `Cannot reset "${key}" — it is a platform enforcement.` };
    }

    await this.deletePref(userId, key);
    const defaultVal = PLATFORM_DEFAULTS[key];
    return {
      success: true,
      message: `Preference "${key}" reset to default: ${JSON.stringify(defaultVal)}.`,
    };
  }

  private async getUserPrefs(userId: string): Promise<Record<string, unknown>> {
    const rows = await this.db
      .select({ key: preferencesTable.key, value: preferencesTable.value })
      .from(preferencesTable)
      .where(eq(preferencesTable.userId, userId));

    const result: Record<string, unknown> = {};
    for (const row of rows) {
      result[row.key] = row.value;
    }
    return result;
  }

  private async upsertPref(userId: string, key: string, value: unknown): Promise<void> {
    // Single-round-trip upsert using INSERT … ON CONFLICT DO UPDATE.
    // Previously this was two queries (SELECT + INSERT/UPDATE), which doubled
    // the DB round-trips and introduced a TOCTOU window under concurrent writes.
    await this.db
      .insert(preferencesTable)
      .values({
        userId,
        key,
        value: value as never,
        mutable: true,
      })
      .onConflictDoUpdate({
        target: [preferencesTable.userId, preferencesTable.key],
        set: {
          value: sql`excluded.value`,
          updatedAt: sql`now()`,
        },
      });
    this.logger.debug(`Upserted preference "${key}" for user ${userId}`);
  }

  private async deletePref(userId: string, key: string): Promise<void> {
    await this.db
      .delete(preferencesTable)
      .where(and(eq(preferencesTable.userId, userId), eq(preferencesTable.key, key)));
    this.logger.debug(`Deleted preference "${key}" for user ${userId}`);
  }
}
