import type { AiAgent, AiProvider } from '@wa-ticketing/shared';
import type postgres from 'postgres';
import { sql } from '../db.js';
import { isOpenAiConfigured } from './openaiService.js';
import { isOpenRouterConfigured } from './openrouterService.js';

export type AiAgentSecretRow = {
  id: string;
  display_name: string;
  provider: AiProvider;
  api_key: string;
  base_url: string | null;
  model_id: string;
  persona_system: string;
  guardrails: string;
  created_at: string;
  updated_at: string;
};

type AiAgentListRow = AiAgentSecretRow & { knowledge_module_ids: string[] | null };

function maskHint(apiKey: string): string | null {
  const t = apiKey.trim();
  if (!t) return null;
  if (t.length <= 4) return '****';
  return `****${t.slice(-4)}`;
}

function toPublic(row: AiAgentSecretRow, knowledge_module_ids: string[] = []): AiAgent {
  const has = Boolean(row.api_key?.trim());
  return {
    id: row.id,
    display_name: row.display_name,
    provider: row.provider,
    base_url: row.base_url,
    model_id: row.model_id,
    persona_system: row.persona_system,
    guardrails: row.guardrails,
    knowledge_module_ids,
    has_api_key: has,
    api_key_hint: has ? maskHint(row.api_key) : null,
    created_at: row.created_at,
    updated_at: row.updated_at,
  };
}

async function setKnowledgeModules(agentId: string, moduleIds: string[]): Promise<void> {
  const uniq = [...new Set(moduleIds.filter(Boolean))];
  await sql.begin(async txn => {
    const q = txn as unknown as postgres.Sql;
    await q`DELETE FROM ai_agent_knowledge_modules WHERE ai_agent_id = ${agentId}`;
    for (const mid of uniq) {
      await q`
        INSERT INTO ai_agent_knowledge_modules (ai_agent_id, knowledge_module_id)
        VALUES (${agentId}, ${mid})
      `;
    }
  });
}

export const aiAgentService = {
  async list(): Promise<AiAgent[]> {
    const rows = await sql<AiAgentListRow[]>`
      SELECT
        a.id,
        a.display_name,
        a.provider,
        a.api_key,
        a.base_url,
        a.model_id,
        a.persona_system,
        a.guardrails,
        a.created_at,
        a.updated_at,
        COALESCE(km.ids, ARRAY[]::uuid[]) AS knowledge_module_ids
      FROM ai_agents a
      LEFT JOIN LATERAL (
        SELECT array_agg(akm.knowledge_module_id ORDER BY m.sort_order ASC, m.title ASC) AS ids
        FROM ai_agent_knowledge_modules akm
        INNER JOIN knowledge_modules m ON m.id = akm.knowledge_module_id
        WHERE akm.ai_agent_id = a.id
      ) km ON true
      ORDER BY a.display_name ASC
    `;
    return rows.map(r =>
      toPublic(r, (r.knowledge_module_ids ?? []).map(String)),
    );
  },

  async getById(id: string): Promise<AiAgent | null> {
    const [row] = await sql<[AiAgentListRow]>`
      SELECT
        a.id,
        a.display_name,
        a.provider,
        a.api_key,
        a.base_url,
        a.model_id,
        a.persona_system,
        a.guardrails,
        a.created_at,
        a.updated_at,
        COALESCE(km.ids, ARRAY[]::uuid[]) AS knowledge_module_ids
      FROM ai_agents a
      LEFT JOIN LATERAL (
        SELECT array_agg(akm.knowledge_module_id ORDER BY m.sort_order ASC, m.title ASC) AS ids
        FROM ai_agent_knowledge_modules akm
        INNER JOIN knowledge_modules m ON m.id = akm.knowledge_module_id
        WHERE akm.ai_agent_id = a.id
      ) km ON true
      WHERE a.id = ${id}
    `;
    return row ? toPublic(row, (row.knowledge_module_ids ?? []).map(String)) : null;
  },

  async getByIdWithSecret(id: string): Promise<AiAgentSecretRow | null> {
    const [row] = await sql<[AiAgentSecretRow]>`
      SELECT id, display_name, provider, api_key, base_url, model_id, persona_system, guardrails, created_at, updated_at
      FROM ai_agents WHERE id = ${id}
    `;
    return row ?? null;
  },

  /** Module ids this agent is allowed to use (talkback intersects with company-enabled modules). */
  async getKnowledgeModuleIds(agentId: string): Promise<string[]> {
    const rows = await sql<Array<{ knowledge_module_id: string }>>`
      SELECT akm.knowledge_module_id
      FROM ai_agent_knowledge_modules akm
      INNER JOIN knowledge_modules m ON m.id = akm.knowledge_module_id
      WHERE akm.ai_agent_id = ${agentId}
      ORDER BY m.sort_order ASC, m.title ASC
    `;
    return rows.map(r => String(r.knowledge_module_id));
  },

  /** Default agent for talkback (includes api_key). */
  async getDefaultAgentForCompany(companyId: string): Promise<AiAgentSecretRow | null> {
    const [row] = await sql<[AiAgentSecretRow]>`
      SELECT a.id, a.display_name, a.provider, a.api_key, a.base_url, a.model_id, a.persona_system, a.guardrails, a.created_at, a.updated_at
      FROM ai_agents a
      INNER JOIN company_ai_agents c ON c.ai_agent_id = a.id
      WHERE c.company_id = ${companyId}
    `;
    return row ?? null;
  },

  /** True if company has an agent with a key, or a platform LLM provider is configured. */
  async isRunnableForCompany(companyId: string): Promise<boolean> {
    const agent = await this.getDefaultAgentForCompany(companyId);
    if (agent?.api_key?.trim()) return true;
    return (await isOpenAiConfigured()) || (await isOpenRouterConfigured());
  },

  async create(opts: {
    display_name: string;
    provider: AiProvider;
    api_key: string;
    base_url?: string | null;
    model_id: string;
    persona_system?: string;
    guardrails?: string;
    knowledge_module_ids?: string[];
  }): Promise<AiAgent> {
    const dn = opts.display_name.trim();
    if (!dn) throw Object.assign(new Error('display_name is required'), { statusCode: 400 });
    const [row] = await sql<[AiAgentSecretRow]>`
      INSERT INTO ai_agents (display_name, provider, api_key, base_url, model_id, persona_system, guardrails)
      VALUES (
        ${dn},
        ${opts.provider},
        ${opts.api_key ?? ''},
        ${opts.base_url?.trim() || null},
        ${opts.model_id.trim()},
        ${opts.persona_system ?? ''},
        ${opts.guardrails ?? ''}
      )
      RETURNING id, display_name, provider, api_key, base_url, model_id, persona_system, guardrails, created_at, updated_at
    `;
    const km = opts.knowledge_module_ids ?? [];
    if (km.length) await setKnowledgeModules(row.id, km);
    const created = await this.getById(row.id);
    if (!created) throw Object.assign(new Error('Failed to load created agent'), { statusCode: 500 });
    return created;
  },

  async update(
    id: string,
    opts: Partial<{
      display_name: string;
      provider: AiProvider;
      api_key: string;
      base_url: string | null;
      model_id: string;
      persona_system: string;
      guardrails: string;
      knowledge_module_ids: string[];
    }>,
  ): Promise<AiAgent | null> {
    const cur = await this.getByIdWithSecret(id);
    if (!cur) return null;

    const display_name = opts.display_name !== undefined ? opts.display_name.trim() : cur.display_name;
    if (!display_name) throw Object.assign(new Error('display_name is required'), { statusCode: 400 });
    const provider = opts.provider ?? cur.provider;
    let api_key = cur.api_key;
    if (opts.api_key !== undefined) {
      api_key = opts.api_key;
    }
    const base_url =
      opts.base_url !== undefined ? (opts.base_url?.trim() || null) : cur.base_url;
    const model_id = opts.model_id !== undefined ? opts.model_id.trim() : cur.model_id;
    const persona_system = opts.persona_system !== undefined ? opts.persona_system : cur.persona_system;
    const guardrails = opts.guardrails !== undefined ? opts.guardrails : cur.guardrails;

    const [row] = await sql<[AiAgentSecretRow]>`
      UPDATE ai_agents SET
        display_name = ${display_name},
        provider = ${provider},
        api_key = ${api_key},
        base_url = ${base_url},
        model_id = ${model_id},
        persona_system = ${persona_system},
        guardrails = ${guardrails},
        updated_at = NOW()
      WHERE id = ${id}
      RETURNING id, display_name, provider, api_key, base_url, model_id, persona_system, guardrails, created_at, updated_at
    `;
    if (!row) return null;
    if (opts.knowledge_module_ids !== undefined) {
      await setKnowledgeModules(id, opts.knowledge_module_ids);
    }
    return this.getById(id);
  },

  async delete(id: string): Promise<boolean> {
    const rows = await sql<{ id: string }[]>`
      DELETE FROM ai_agents WHERE id = ${id} RETURNING id
    `;
    return rows.length > 0;
  },

  async getCompanyDefaultAgentId(companyId: string): Promise<string | null> {
    const [row] = await sql<Array<{ ai_agent_id: string }>>`
      SELECT ai_agent_id FROM company_ai_agents WHERE company_id = ${companyId}
    `;
    return row?.ai_agent_id ?? null;
  },

  async setCompanyDefaultAgent(companyId: string, aiAgentId: string | null): Promise<void> {
    if (aiAgentId === null) {
      await sql`DELETE FROM company_ai_agents WHERE company_id = ${companyId}`;
      return;
    }
    const agent = await this.getByIdWithSecret(aiAgentId);
    if (!agent) throw Object.assign(new Error('AI agent not found'), { statusCode: 404 });

    await sql`
      INSERT INTO company_ai_agents (company_id, ai_agent_id, updated_at)
      VALUES (${companyId}, ${aiAgentId}, NOW())
      ON CONFLICT (company_id) DO UPDATE SET
        ai_agent_id = EXCLUDED.ai_agent_id,
        updated_at = NOW()
    `;
  },
};
