import { many, one, query } from '../../db/pool.js';
import { hashPassword, verifyPassword } from '../../auth/password.js';
import { signAgentToken } from '../../auth/jwt.js';
import { conflict, notFound, unauthorized } from '../../utils/errors.js';
import { redis, KEYS } from '../../redis/client.js';
import type { AgentDTO, Presence } from '../types.js';

function toAgentDTO(row: any, activeChats?: number): AgentDTO {
  return {
    id: row.id,
    name: row.name,
    email: row.email,
    avatarUrl: row.avatar_url ?? null,
    role: row.role,
    presence: row.presence,
    maxConcurrentChats: row.max_concurrent_chats,
    ...(activeChats !== undefined ? { activeChats } : {}),
  };
}

export async function login(email: string, password: string): Promise<{ token: string; agent: AgentDTO }> {
  const row = await one(`SELECT * FROM agents WHERE email = $1`, [email.trim().toLowerCase()]);
  // Same error either way — don't leak which emails exist.
  if (!row) throw unauthorized('Invalid email or password');
  const ok = await verifyPassword(password, row.password_hash);
  if (!ok) throw unauthorized('Invalid email or password');

  await query(`UPDATE agents SET last_seen_at = now() WHERE id = $1`, [row.id]);
  const agent = toAgentDTO(row);
  const token = signAgentToken({ sub: agent.id, email: agent.email, name: agent.name, role: agent.role });
  return { token, agent };
}

export async function createAgent(input: {
  email: string;
  password: string;
  name: string;
  role?: 'agent' | 'admin';
  maxConcurrentChats?: number;
}): Promise<AgentDTO> {
  const email = input.email.trim().toLowerCase();
  const exists = await one(`SELECT id FROM agents WHERE email = $1`, [email]);
  if (exists) throw conflict('An agent with that email already exists');

  const row = await one(
    `INSERT INTO agents (email, password_hash, name, role, max_concurrent_chats)
     VALUES ($1,$2,$3,$4,COALESCE($5, 5)) RETURNING *`,
    [email, await hashPassword(input.password), input.name.trim(), input.role ?? 'agent', input.maxConcurrentChats ?? null],
  );
  return toAgentDTO(row);
}

export async function getAgent(id: string): Promise<AgentDTO> {
  const row = await one(`SELECT * FROM agents WHERE id = $1`, [id]);
  if (!row) throw notFound('Agent not found');
  return toAgentDTO(row);
}

/** Team roster with live presence + load — powers the transfer picker. */
export async function listAgents(excludeId?: string): Promise<AgentDTO[]> {
  const rows = await many(
    `SELECT a.*, (SELECT COUNT(*) FROM conversations c
                   WHERE c.agent_id = a.id AND c.status = 'active') AS active_chats
       FROM agents a
      WHERE ($1::uuid IS NULL OR a.id <> $1)
      ORDER BY a.name ASC`,
    [excludeId ?? null],
  );
  const presence = await redis.hgetall(KEYS.agentPresence).catch(() => ({} as Record<string, string>));
  return rows.map((r: any) => ({
    ...toAgentDTO(r, Number(r.active_chats)),
    // Redis wins: it reflects live sockets, the DB column is the last persisted value.
    presence: (presence[r.id] as Presence) ?? r.presence,
  }));
}

export async function setPresence(agentId: string, presence: Presence): Promise<void> {
  await Promise.all([
    query(`UPDATE agents SET presence = $2, last_seen_at = now() WHERE id = $1`, [agentId, presence]),
    presence === 'offline'
      ? redis.hdel(KEYS.agentPresence, agentId)
      : redis.hset(KEYS.agentPresence, agentId, presence),
  ]);
}

export async function activeChatCount(agentId: string): Promise<number> {
  const row = await one(
    `SELECT COUNT(*) AS n FROM conversations WHERE agent_id = $1 AND status = 'active'`,
    [agentId],
  );
  return Number(row?.n ?? 0);
}

/** Capacity check used before an agent accepts another chat from the queue. */
export async function hasCapacity(agentId: string): Promise<{ ok: boolean; active: number; max: number }> {
  const agent = await getAgent(agentId);
  const active = await activeChatCount(agentId);
  return { ok: active < agent.maxConcurrentChats, active, max: agent.maxConcurrentChats };
}
