import { db } from '@server/db/drizzle';
import { sql } from 'drizzle-orm';
import { initDatabase } from '@server/db/init';
import { encrypt, decrypt } from '@server/utils/crypto';

import { childLogger } from '@server/logger';
const log = childLogger('svc.provider-keys');

export const SUPPORTED_PROVIDERS = [
  { name: 'openai', displayName: 'OpenAI', envVar: 'OPENAI_API_KEY', description: 'GPT models, TTS, Whisper STT' },
  { name: 'openrouter', displayName: 'OpenRouter', envVar: 'OPENROUTER_API_KEY', description: 'Multi-provider LLM gateway' },
  { name: 'elevenlabs', displayName: 'ElevenLabs', envVar: 'ELEVENLABS_API_KEY', description: 'High-quality multilingual TTS' },
  // Provider canonical name is `deepgram` (matches DEEPGRAM_API_KEY env var
  // and Deepgram's own product taxonomy where Aura is the TTS family inside
  // a single Deepgram account). The originally-spec'd `deepgram_aura` would
  // create a misleading per-product provider row; we keep one `deepgram`
  // provider and surface Aura voices underneath it.
  { name: 'deepgram', displayName: 'Deepgram', envVar: 'DEEPGRAM_API_KEY', description: 'Aura TTS & Nova STT' },
  { name: 'sarvam', displayName: 'Sarvam AI', envVar: 'SARVAM_API_KEY', description: 'Indic-language TTS (bulbul:v3)' },
] as const;

export type ProviderName = typeof SUPPORTED_PROVIDERS[number]['name'];

export interface ProviderKeyInfo {
  id: string;
  provider_name: string;
  display_name: string;
  description: string;
  key_hint: string;
  is_active: boolean;
  has_env_fallback: boolean;
  created_at: string;
  updated_at: string;
}

function makeKeyHint(key: string): string {
  if (key.length <= 8) return '****';
  return key.slice(0, 6) + '...' + key.slice(-4);
}

export async function saveProviderKey(
  restaurantId: string,
  providerName: string,
  apiKey: string
): Promise<ProviderKeyInfo> {
  await initDatabase();

  const provider = SUPPORTED_PROVIDERS.find(p => p.name === providerName);
  if (!provider) throw new Error(`Unsupported provider: ${providerName}`);
  if (!apiKey?.trim()) throw new Error('API key is required');

  const encrypted = encrypt(apiKey.trim());
  const hint = makeKeyHint(apiKey.trim());

  /* raw: INSERT INTO restaurant_api_keys (...) VALUES (...) ON CONFLICT (...) DO UPDATE SET ... RETURNING ... */
  const { rows } = await db.execute(sql`
    INSERT INTO restaurant_api_keys (restaurant_id, provider_name, encrypted_api_key, key_hint)
    VALUES (${restaurantId}, ${providerName}, ${encrypted}, ${hint})
    ON CONFLICT (restaurant_id, provider_name)
    DO UPDATE SET encrypted_api_key = ${encrypted}, key_hint = ${hint}, is_active = true, updated_at = NOW()
    RETURNING id, provider_name, key_hint, is_active, created_at, updated_at
  `);

  const row = rows[0] as unknown as {
    id: string; provider_name: string; key_hint: string;
    is_active: boolean; created_at: string; updated_at: string;
  };

  return {
    ...row,
    display_name: provider.displayName,
    description: provider.description,
    has_env_fallback: !!process.env[provider.envVar],
  };
}

export async function listProviderKeys(restaurantId: string): Promise<ProviderKeyInfo[]> {
  await initDatabase();

  /* raw: SELECT id, provider_name, key_hint, is_active, created_at, updated_at FROM restaurant_api_keys WHERE restaurant_id = $1 ORDER BY provider_name */
  const { rows } = await db.execute(sql`
    SELECT id, provider_name, key_hint, is_active, created_at, updated_at
    FROM restaurant_api_keys WHERE restaurant_id = ${restaurantId} ORDER BY provider_name
  `);

  const typedRows = rows as unknown as {
    id: string; provider_name: string; key_hint: string;
    is_active: boolean; created_at: string; updated_at: string;
  }[];

  const savedMap = new Map(typedRows.map(r => [r.provider_name, r]));

  const HIDDEN_PROVIDERS = new Set(['elevenlabs', 'deepgram', 'sarvam']);

  return SUPPORTED_PROVIDERS.filter(p => !HIDDEN_PROVIDERS.has(p.name)).map(p => {
    const saved = savedMap.get(p.name);
    return {
      id: saved?.id || '',
      provider_name: p.name,
      display_name: p.displayName,
      description: p.description,
      key_hint: saved?.key_hint || '',
      is_active: saved?.is_active || false,
      has_env_fallback: !!process.env[p.envVar],
      created_at: saved?.created_at || '',
      updated_at: saved?.updated_at || '',
    };
  });
}

export async function deleteProviderKey(restaurantId: string, providerName: string): Promise<void> {
  await initDatabase();
  /* raw: DELETE FROM restaurant_api_keys WHERE restaurant_id = $1 AND provider_name = $2 */
  const result = await db.execute(sql`DELETE FROM restaurant_api_keys WHERE restaurant_id = ${restaurantId} AND provider_name = ${providerName}`);
  if ((result.rowCount ?? 0) === 0) throw new Error('API key not found');
}

export async function resolveApiKey(
  restaurantId: string | null,
  providerName: string
): Promise<string | null> {
  const provider = SUPPORTED_PROVIDERS.find(p => p.name === providerName);

  if (restaurantId) {
    try {
      await initDatabase();
      /* raw: SELECT encrypted_api_key, is_active FROM restaurant_api_keys WHERE restaurant_id = $1 AND provider_name = $2 */
      const { rows } = await db.execute(sql`
        SELECT encrypted_api_key, is_active FROM restaurant_api_keys
        WHERE restaurant_id = ${restaurantId} AND provider_name = ${providerName}
      `);
      const row = rows[0] as unknown as { encrypted_api_key: string; is_active: boolean } | undefined;
      if (row?.is_active && row.encrypted_api_key) {
        try {
          return decrypt(row.encrypted_api_key);
        } catch {
          log.warn({ providerName }, 'failed to decrypt key, falling back to env');
        }
      }
    } catch {
      // fall through to env var
    }
  }

  if (provider) {
    return process.env[provider.envVar] || null;
  }
  return null;
}

export async function resolveApiKeyByEnvVar(
  restaurantId: string | null,
  envVarName: string
): Promise<string | null> {
  const provider = SUPPORTED_PROVIDERS.find(p => p.envVar === envVarName);
  if (provider) {
    return resolveApiKey(restaurantId, provider.name);
  }
  return process.env[envVarName] || null;
}
