import type { ResultSetHeader, RowDataPacket } from "mysql2/promise";
import { pool } from "../db/mysql.js";
import { decrypt, encrypt, lastFour } from "../crypto/secretbox.js";
import type {
  CreateAccountInput,
  ExchangeAccountPublic,
  ExchangeAccountRow,
  UpdateAccountInput,
} from "./types.js";

/**
 * Cofre de chaves: CRUD de `exchange_accounts`. É a única porta de entrada
 * para gravar/ler credenciais de exchange. Segredos (`api_secret`, senha de
 * proxy) entram em claro e saem criptografados; a visão pública nunca os
 * revela (só os últimos 4 dígitos do secret).
 */

type Row = ExchangeAccountRow & RowDataPacket;

const SELECT_COLS = `id, label, exchange, api_key, api_secret_enc, binance_user_id,
  proxy_enabled, proxy_protocol, proxy_host, proxy_port, proxy_user, proxy_pass_enc,
  active, created_at, updated_at`;

/** Converte a linha crua na visão pública (sem segredos). */
export function toPublic(row: ExchangeAccountRow): ExchangeAccountPublic {
  // Decifra o secret só para extrair os últimos 4 dígitos exibíveis.
  const last4 = lastFour(decrypt(row.api_secret_enc));
  return {
    id: row.id,
    label: row.label,
    exchange: row.exchange,
    apiKey: row.api_key,
    apiSecretLast4: last4,
    binanceUserId: row.binance_user_id,
    proxy: row.proxy_enabled || row.proxy_host
      ? {
          enabled: !!row.proxy_enabled,
          protocol: row.proxy_protocol,
          host: row.proxy_host,
          port: row.proxy_port,
          user: row.proxy_user,
          hasPassword: row.proxy_pass_enc != null,
        }
      : null,
    active: !!row.active,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

/** Busca a linha crua por id (uso interno: factory de client, etc.). */
export async function findRowById(id: number): Promise<ExchangeAccountRow | null> {
  const [rows] = await pool.query<Row[]>(
    `SELECT ${SELECT_COLS} FROM exchange_accounts WHERE id = :id`,
    { id },
  );
  return rows[0] ?? null;
}

export async function getById(id: number): Promise<ExchangeAccountPublic | null> {
  const row = await findRowById(id);
  return row ? toPublic(row) : null;
}

export async function list(): Promise<ExchangeAccountPublic[]> {
  const [rows] = await pool.query<Row[]>(
    `SELECT ${SELECT_COLS} FROM exchange_accounts ORDER BY id`,
  );
  return rows.map(toPublic);
}

export async function create(input: CreateAccountInput): Promise<number> {
  const p = input.proxy ?? null;
  const [res] = await pool.query<ResultSetHeader>(
    `INSERT INTO exchange_accounts
      (label, exchange, api_key, api_secret_enc, binance_user_id,
       proxy_enabled, proxy_protocol, proxy_host, proxy_port, proxy_user, proxy_pass_enc,
       active)
     VALUES
      (:label, :exchange, :apiKey, :apiSecretEnc, :binanceUserId,
       :proxyEnabled, :proxyProtocol, :proxyHost, :proxyPort, :proxyUser, :proxyPassEnc,
       :active)`,
    {
      label: input.label,
      exchange: input.exchange,
      apiKey: input.apiKey,
      apiSecretEnc: encrypt(input.apiSecret),
      binanceUserId: input.binanceUserId ?? null,
      proxyEnabled: p?.enabled ? 1 : 0,
      proxyProtocol: p?.protocol ?? null,
      proxyHost: p?.host ?? null,
      proxyPort: p?.port ?? null,
      proxyUser: p?.user ?? null,
      proxyPassEnc: p?.pass ? encrypt(p.pass) : null,
      active: input.active === false ? 0 : 1,
    },
  );
  return res.insertId;
}

/**
 * Atualização parcial. Só toca nos campos presentes no input. `apiSecret`
 * vazio/ausente preserva o atual. `proxy === null` remove o proxy;
 * `proxy.pass` ausente preserva a senha existente.
 */
export async function update(id: number, input: UpdateAccountInput): Promise<boolean> {
  const sets: string[] = [];
  const params: Record<string, string | number | Buffer | null> = { id };

  if (input.label !== undefined) {
    sets.push("label = :label");
    params.label = input.label;
  }
  if (input.apiKey !== undefined) {
    sets.push("api_key = :apiKey");
    params.apiKey = input.apiKey;
  }
  if (input.apiSecret) {
    sets.push("api_secret_enc = :apiSecretEnc");
    params.apiSecretEnc = encrypt(input.apiSecret);
  }
  if (input.binanceUserId !== undefined) {
    sets.push("binance_user_id = :binanceUserId");
    params.binanceUserId = input.binanceUserId;
  }
  if (input.active !== undefined) {
    sets.push("active = :active");
    params.active = input.active ? 1 : 0;
  }

  if (input.proxy !== undefined) {
    if (input.proxy === null) {
      // Remove o proxy por completo.
      sets.push(
        "proxy_enabled = 0",
        "proxy_protocol = NULL",
        "proxy_host = NULL",
        "proxy_port = NULL",
        "proxy_user = NULL",
        "proxy_pass_enc = NULL",
      );
    } else {
      const p = input.proxy;
      sets.push(
        "proxy_enabled = :proxyEnabled",
        "proxy_protocol = :proxyProtocol",
        "proxy_host = :proxyHost",
        "proxy_port = :proxyPort",
        "proxy_user = :proxyUser",
      );
      params.proxyEnabled = p.enabled ? 1 : 0;
      params.proxyProtocol = p.protocol;
      params.proxyHost = p.host;
      params.proxyPort = p.port;
      params.proxyUser = p.user ?? null;
      // Só regrava a senha se veio uma nova.
      if (p.pass) {
        sets.push("proxy_pass_enc = :proxyPassEnc");
        params.proxyPassEnc = encrypt(p.pass);
      }
    }
  }

  if (sets.length === 0) return true; // nada a atualizar

  const [res] = await pool.query<ResultSetHeader>(
    `UPDATE exchange_accounts SET ${sets.join(", ")} WHERE id = :id`,
    params,
  );
  return res.affectedRows > 0;
}

export async function remove(id: number): Promise<boolean> {
  const [res] = await pool.query<ResultSetHeader>(
    `DELETE FROM exchange_accounts WHERE id = :id`,
    { id },
  );
  return res.affectedRows > 0;
}
