import axios from "axios";

import { buildCommanderHeaders, resolveCommanderBaseUrl } from "./commanderHttp";

export type BancoPontosEstaAtivoFiltro = "all" | "true" | "false";

export type BancoPontosSaldoItem = {
  parceiroId: string;
  nome: string;
  codinome: string | null;
  estaAtivo: boolean;
  logoUrl: string | null;
  /** URL pública da foto (Commander pode enviar junto com `logoUrl`). */
  fotoUrl?: string | null;
  saldo: number;
};

export type BancoPontosExtratoItem = {
  id: string;
  parceiroId: string;
  fechamentoId: string | null;
  tipo: "credito" | "debito";
  quantidade: number;
  descricao: string;
  criadoEm: string;
  /** Mês da competência do fechamento (1–12), se a linha estiver vinculada a um fechamento. */
  competenciaMes: number | null;
  /** Ano da competência do fechamento, se houver. */
  competenciaAno: number | null;
};

export type BancoPontosParceiroExtrato = {
  parceiroId: string;
  nome: string;
  codinome: string | null;
  logoUrl: string | null;
  fotoUrl?: string | null;
  saldo: number;
};

type MetaResponse = {
  total: number;
  paginaAtual: number;
  totalPaginas: number;
};

export type ListarBancoPontosSaldosResponse = {
  data: BancoPontosSaldoItem[];
  meta: MetaResponse;
};

export type ListarBancoPontosExtratoResponse = {
  parceiro: BancoPontosParceiroExtrato;
  data: BancoPontosExtratoItem[];
  meta: MetaResponse;
};

type ApiErrorShape = {
  error?: {
    message?: string;
  };
};

function pickLogoUrl(row: { logoUrl?: unknown; logo_url?: unknown }): string | null {
  const v = row.logoUrl ?? row.logo_url;
  if (v == null) return null;
  const s = String(v).trim();
  return s.length > 0 ? s : null;
}

function pickSaldo(row: { saldo?: unknown; saldo_atual?: unknown }): number {
  const v = row.saldo ?? row.saldo_atual;
  if (v == null || v === "") return 0;
  const n = Number(v);
  return Number.isFinite(n) ? n : 0;
}

function pickFotoUrl(raw: { fotoUrl?: unknown; foto_url?: unknown }): string | null {
  const v = raw.fotoUrl ?? raw.foto_url;
  if (v == null) return null;
  const s = String(v).trim();
  return s.length > 0 ? s : null;
}

function normalizeSaldoItem(raw: Record<string, unknown>): BancoPontosSaldoItem {
  const logo = pickLogoUrl(raw);
  const foto = pickFotoUrl(raw) ?? logo;
  return {
    parceiroId: String(raw.parceiroId ?? raw.parceiro_id ?? ""),
    nome: String(raw.nome ?? ""),
    codinome: raw.codinome != null ? String(raw.codinome) : null,
    estaAtivo: Boolean(raw.estaAtivo ?? raw.esta_ativo),
    logoUrl: foto,
    fotoUrl: foto,
    saldo: pickSaldo(raw),
  };
}

function normalizeParceiroExtrato(raw: Record<string, unknown>): BancoPontosParceiroExtrato {
  const logo = pickLogoUrl(raw);
  const foto = pickFotoUrl(raw) ?? logo;
  return {
    parceiroId: String(raw.parceiroId ?? raw.parceiro_id ?? ""),
    nome: String(raw.nome ?? ""),
    codinome: raw.codinome != null ? String(raw.codinome) : null,
    logoUrl: foto,
    fotoUrl: foto,
    saldo: pickSaldo(raw),
  };
}

function pickNullableInt(v: unknown): number | null {
  if (v == null || v === "") return null;
  const n = Number(v);
  return Number.isFinite(n) ? Math.trunc(n) : null;
}

export function normalizeBancoPontosExtratoItem(raw: Record<string, unknown>): BancoPontosExtratoItem {
  const fechamentoRaw = raw.fechamentoId ?? raw.fechamento_id;
  return {
    id: String(raw.id ?? ""),
    parceiroId: String(raw.parceiroId ?? raw.parceiro_id ?? ""),
    fechamentoId: fechamentoRaw != null && String(fechamentoRaw).trim() !== "" ? String(fechamentoRaw) : null,
    tipo: raw.tipo === "debito" ? "debito" : "credito",
    quantidade: Number(raw.quantidade ?? 0),
    descricao: String(raw.descricao ?? ""),
    criadoEm: String(raw.criadoEm ?? raw.criado_em ?? ""),
    competenciaMes: pickNullableInt(raw.competenciaMes ?? raw.competencia_mes),
    competenciaAno: pickNullableInt(raw.competenciaAno ?? raw.competencia_ano),
  };
}

export type ListarBancoPontosSaldosParams = {
  estaAtivo?: BancoPontosEstaAtivoFiltro;
  busca?: string;
  page: number;
  perPage: number;
};

export type ListarBancoPontosExtratoParams = {
  page: number;
  perPage: number;
};

export type ExportarBancoPontosExtratoResponse = {
  buffer: ArrayBuffer;
  filename: string | null;
};

class FechamentoBancoPontosService {
  private extractFilenameFromContentDisposition(value: string | undefined): string | null {
    if (!value) return null;
    const utf8Match = value.match(/filename\*=UTF-8''([^;]+)/i);
    if (utf8Match?.[1]) {
      try {
        return decodeURIComponent(utf8Match[1]);
      } catch {
        return utf8Match[1];
      }
    }
    const regularMatch = value.match(/filename="?([^";]+)"?/i);
    return regularMatch?.[1] ?? null;
  }

  private messageFromResponseData(data: unknown): string | null {
    if (data == null) return null;
    if (typeof data === "object" && !(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) {
      const msg = (data as ApiErrorShape).error?.message;
      return typeof msg === "string" && msg.trim() ? msg : null;
    }
    if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
      const buf = data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
      if (buf.byteLength === 0) return null;
      try {
        const text = new TextDecoder().decode(buf);
        const trimmed = text.trim();
        if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
          const json = JSON.parse(trimmed) as ApiErrorShape;
          if (typeof json.error?.message === "string" && json.error.message.trim()) {
            return json.error.message;
          }
        }
      } catch {
        return null;
      }
    }
    return null;
  }

  private handleAxiosError(error: unknown, fallback: string): never {
    if (axios.isAxiosError(error)) {
      if (!error.response) {
        throw new Error(
          "Não foi possível conectar na API do Commander. Verifique VITE_API_BASE_URL_COMMANDER e se o backend está acessível.",
        );
      }
      const status = error.response.status;
      const fromBody =
        this.messageFromResponseData(error.response.data) ??
        (status === 404
          ? "Recurso não encontrado. Confirme se o backend está atualizado (rota de exportação)."
          : null);
      const message = (fromBody ?? error.message ?? fallback) as string;
      throw new Error(message);
    }
    throw new Error(fallback);
  }

  async listarSaldos(params: ListarBancoPontosSaldosParams): Promise<ListarBancoPontosSaldosResponse> {
    try {
      const headers = await buildCommanderHeaders();
      const baseUrl = resolveCommanderBaseUrl();
      const response = await axios.get<ListarBancoPontosSaldosResponse>(`${baseUrl}/banco-pontos`, {
        headers,
        params: {
          estaAtivo: params.estaAtivo ?? "all",
          busca: params.busca?.trim() || undefined,
          page: params.page,
          perPage: params.perPage,
        },
      });
      const body = response.data;
      return {
        ...body,
        data: (body.data ?? []).map((row) => normalizeSaldoItem(row as unknown as Record<string, unknown>)),
      };
    } catch (error) {
      this.handleAxiosError(error, "Erro ao listar saldos do banco de pontos.");
    }
  }

  async listarExtrato(
    parceiroId: string,
    params: ListarBancoPontosExtratoParams,
  ): Promise<ListarBancoPontosExtratoResponse> {
    try {
      const headers = await buildCommanderHeaders();
      const baseUrl = resolveCommanderBaseUrl();
      const response = await axios.get<ListarBancoPontosExtratoResponse>(
        `${baseUrl}/banco-pontos/${parceiroId}/extrato`,
        {
          headers,
          params: {
            page: params.page,
            perPage: params.perPage,
          },
        },
      );
      const body = response.data;
      const parceiroRaw = body.parceiro as unknown;
      const parceiroNorm =
        parceiroRaw != null && typeof parceiroRaw === "object"
          ? normalizeParceiroExtrato(parceiroRaw as Record<string, unknown>)
          : body.parceiro;
      return {
        ...body,
        parceiro: parceiroNorm,
        data: (body.data ?? []).map((row) => normalizeBancoPontosExtratoItem(row as unknown as Record<string, unknown>)),
      };
    } catch (error) {
      this.handleAxiosError(error, "Erro ao carregar extrato do banco de pontos.");
    }
  }

  async exportarExtrato(parceiroId: string): Promise<ExportarBancoPontosExtratoResponse> {
    try {
      const headers = await buildCommanderHeaders();
      const baseUrl = resolveCommanderBaseUrl();
      const response = await axios.get<ArrayBuffer>(
        `${baseUrl}/banco-pontos/${parceiroId}/extrato/exportar`,
        {
          headers,
          responseType: "arraybuffer",
        },
      );
      const buf = response.data;
      if (buf.byteLength < 4) {
        throw new Error("Resposta vazia ao exportar planilha.");
      }
      const bytes = new Uint8Array(buf);
      const headText = new TextDecoder().decode(bytes.subarray(0, Math.min(256, bytes.length))).trim();
      if (headText.startsWith("{")) {
        let json: ApiErrorShape;
        try {
          json = JSON.parse(new TextDecoder().decode(bytes)) as ApiErrorShape;
        } catch {
          throw new Error("A API retornou uma resposta inválida ao exportar.");
        }
        const msg = json.error?.message?.trim();
        throw new Error(msg || "Falha ao exportar extrato (resposta JSON). Atualize o Commander.");
      }
      if (bytes[0] !== 0x50 || bytes[1] !== 0x4b) {
        throw new Error("A API não retornou um arquivo XLSX válido (assinatura inválida).");
      }
      return {
        buffer: buf,
        filename: this.extractFilenameFromContentDisposition(response.headers["content-disposition"]),
      };
    } catch (error) {
      if (axios.isAxiosError(error)) {
        this.handleAxiosError(error, "Erro ao exportar extrato do banco de pontos.");
      }
      throw error instanceof Error ? error : new Error("Erro ao exportar extrato do banco de pontos.");
    }
  }
}

export const fechamentoBancoPontosService = new FechamentoBancoPontosService();
