import { executeRows, firstExecuteRow } from '../execute-rows.js';
import { asc, eq, sql } from 'drizzle-orm';
import type { LlmProviderType } from '@/lib/enums/llm-provider-type';
import type { DrizzleClient } from '@/server/db/client';
import { encrypt } from '@/server/db/crypto';
import { llmProviders } from '@/server/db/schema';

export type LlmProviderRow = typeof llmProviders.$inferSelect;

export type LlmProviderPublic = Pick<
  LlmProviderRow,
  'id' | 'slug' | 'name' | 'type' | 'baseUrl' | 'isActive' | 'createdAt' | 'updatedAt'
>;

export function toPublicLlmProvider(row: LlmProviderRow): LlmProviderPublic {
  return {
    id: row.id,
    slug: row.slug,
    name: row.name,
    type: row.type,
    baseUrl: row.baseUrl,
    isActive: row.isActive,
    createdAt: row.createdAt,
    updatedAt: row.updatedAt,
  };
}

export async function getLlmProvider(
  db: DrizzleClient,
  id: string,
): Promise<LlmProviderRow | null> {
  const rows = await db.select().from(llmProviders).where(eq(llmProviders.id, id)).limit(1);
  return rows[0] ?? null;
}

export async function getLlmProviderWithKey(
  db: DrizzleClient,
  id: string,
): Promise<LlmProviderRow | null> {
  return getLlmProvider(db, id);
}

export async function listLlmProviders(db: DrizzleClient): Promise<LlmProviderRow[]> {
  return db.select().from(llmProviders).orderBy(asc(llmProviders.slug));
}

export async function insertLlmProvider(
  db: DrizzleClient,
  input: {
    id?: string;
    slug: string;
    name: string;
    type: LlmProviderType;
    baseUrl: string | null;
    apiKey: string;
    piiKey: string;
  },
): Promise<LlmProviderRow> {
  const [row] = await db
    .insert(llmProviders)
    .values({
      id: input.id,
      slug: input.slug,
      name: input.name,
      type: input.type,
      baseUrl: input.baseUrl,
      apiKeyEnc: encrypt(input.apiKey, input.piiKey),
    })
    .returning();
  if (!row) throw new Error('insertLlmProvider: insert failed');
  return row;
}

export async function updateLlmProvider(
  db: DrizzleClient,
  id: string,
  fields: {
    slug?: string;
    name?: string;
    type?: LlmProviderType;
    baseUrl?: string | null;
    apiKey?: string;
    piiKey?: string;
  },
): Promise<LlmProviderRow | null> {
  const patch: Record<string, unknown> = { updatedAt: new Date() };
  if (fields.slug !== undefined) patch.slug = fields.slug;
  if (fields.name !== undefined) patch.name = fields.name;
  if (fields.type !== undefined) patch.type = fields.type;
  if (fields.baseUrl !== undefined) patch.baseUrl = fields.baseUrl;
  if (fields.apiKey !== undefined && fields.piiKey !== undefined) {
    patch.apiKeyEnc = encrypt(fields.apiKey, fields.piiKey);
  }

  const [row] = await db.update(llmProviders).set(patch).where(eq(llmProviders.id, id)).returning();
  return row ?? null;
}

export async function deleteLlmProvider(db: DrizzleClient, id: string): Promise<boolean> {
  const result = await db
    .delete(llmProviders)
    .where(eq(llmProviders.id, id))
    .returning({ id: llmProviders.id });
  return result.length > 0;
}

export async function findAgentDefinitionSlugsUsingProvider(
  db: DrizzleClient,
  providerId: string,
): Promise<string[]> {
  const result = await db.execute<{ slug: string }>(sql`
    SELECT slug
    FROM agent_definitions
    WHERE EXISTS (
      SELECT 1
      FROM jsonb_array_elements(llm_chain->'chain') AS elem
      WHERE elem->>'llmProviderId' = ${providerId}
    )
  `);
  return executeRows<{ slug: string }>(result).map((r) => r.slug);
}

export async function decryptLlmProviderApiKey(
  db: DrizzleClient,
  providerId: string,
  piiKey: string,
): Promise<string | null> {
  const result = await db.execute<{ api_key: string | null }>(sql`
    SELECT pgp_sym_decrypt(api_key_enc, ${piiKey})::text AS api_key
    FROM llm_providers
    WHERE id = ${providerId}::uuid
  `);
  return firstExecuteRow<{ api_key: string | null }>(result)?.api_key ?? null;
}
