import { readFileSync, existsSync } from 'fs';
import { resolve } from 'path';
import axios, { type AxiosRequestConfig } from 'axios';
import type { JournalMatrixFile } from './omnl-journal-matrix';

export interface IpsasGlAccount {
  glCode: string;
  name: string;
  fineractType: string;
  usage: string;
  ipsasStandards: string[];
  roles: string[];
}

export interface AllowedJournalPair {
  debitGlCode: string;
  creditGlCode: string;
  ipsasRef: string;
  memo?: string;
}

export interface IpsasGlRegistry {
  version: string;
  currencyCode: string;
  accounts: IpsasGlAccount[];
  allowedJournalPairs: AllowedJournalPair[];
  monetaryLayerHints: Record<
    string,
    { primaryGlCodes: string[]; ipsasNarrative: string }
  >;
}

export function loadIpsasRegistryPath(): string {
  return (
    process.env.OMNL_IPSAS_GL_REGISTRY ||
    resolve(__dirname, '../../../../config/omnl-ipsas-gl-registry.json')
  );
}

export function loadIpsasRegistry(): IpsasGlRegistry {
  const p = loadIpsasRegistryPath();
  if (!existsSync(p)) {
    throw new Error(`IPSAS GL registry not found: ${p}`);
  }
  return JSON.parse(readFileSync(p, 'utf8')) as IpsasGlRegistry;
}

export function validateJournalPair(
  registry: IpsasGlRegistry,
  debitGlCode: string,
  creditGlCode: string
): { valid: boolean; ipsasRef?: string; memo?: string } {
  const d = debitGlCode.trim();
  const c = creditGlCode.trim();
  const hit = registry.allowedJournalPairs.find(
    (p) => p.debitGlCode === d && p.creditGlCode === c
  );
  if (hit) {
    return { valid: true, ipsasRef: hit.ipsasRef, memo: hit.memo };
  }
  return { valid: false };
}

/** Prefer static registry; fall back to full journal matrix (T-001…T-008). */
export function validateJournalPairWithMatrix(
  registry: IpsasGlRegistry,
  matrix: JournalMatrixFile | null,
  debitGlCode: string,
  creditGlCode: string
): { valid: boolean; ipsasRef?: string; memo?: string; narrative?: string; source: 'registry' | 'matrix' | 'none' } {
  const r0 = validateJournalPair(registry, debitGlCode, creditGlCode);
  if (r0.valid) {
    return { ...r0, source: 'registry' };
  }
  const d = debitGlCode.trim();
  const c = creditGlCode.trim();
  if (matrix) {
    const hit = matrix.entries.find((e) => e.debitGlCode === d && e.creditGlCode === c);
    if (hit) {
      return {
        valid: true,
        ipsasRef: hit.ipsasRef,
        memo: hit.memo,
        narrative: hit.narrative,
        source: 'matrix',
      };
    }
  }
  return { valid: false, source: 'none' };
}

export function assertGlCodeKnown(registry: IpsasGlRegistry, glCode: string): boolean {
  return registry.accounts.some((a) => a.glCode === glCode.trim());
}

export interface FineractGlAccountRow {
  id: number;
  glCode?: string;
  name?: string;
}

/** GET /glaccounts from Fineract (OMNL). Paginates until exhausted; handles top-level array or pageItems. */
export async function fetchFineractGlAccounts(): Promise<FineractGlAccountRow[]> {
  const base = (process.env.OMNL_FINERACT_BASE_URL || '').replace(/\/$/, '');
  const tenant = process.env.OMNL_FINERACT_TENANT || 'omnl';
  const user =
    process.env.OMNL_FINERACT_USER?.trim() ||
    process.env.OMNL_FINERACT_USERNAME?.trim() ||
    'app.omnl';
  const pass = process.env.OMNL_FINERACT_PASSWORD || '';
  if (!base || !pass) {
    throw new Error('OMNL_FINERACT_BASE_URL and OMNL_FINERACT_PASSWORD required for Fineract GL sync');
  }
  const auth = Buffer.from(`${user}:${pass}`).toString('base64');
  const headers: Record<string, string> = {
    Authorization: `Basic ${auth}`,
    'Fineract-Platform-TenantId': tenant,
    'Content-Type': 'application/json',
  };
  const limit = parseInt(process.env.OMNL_FINERACT_GL_PAGE_LIMIT || '200', 10);
  const maxPages = parseInt(process.env.OMNL_FINERACT_GL_MAX_PAGES || '25', 10);
  const out: FineractGlAccountRow[] = [];
  let offset = 0;
  let pages = 0;
  let previousFirstId: number | undefined;
  for (;;) {
    const url = `${base}/glaccounts?limit=${limit}&offset=${offset}`;
    const cfg: AxiosRequestConfig = { headers, timeout: 25000 };
    const { data } = await axios.get<
      FineractGlAccountRow[] | { pageItems?: FineractGlAccountRow[]; totalFilteredRecords?: number }
    >(url, cfg);
    let batch: FineractGlAccountRow[] = [];
    if (Array.isArray(data)) {
      // Hybx tenant returns the full chart as a top-level array; offset/limit are ignored.
      batch = data;
      out.push(...batch);
      break;
    }
    batch = data.pageItems ?? [];
    if (batch.length === 0) {
      break;
    }
    const firstId = batch[0]?.id;
    if (offset > 0 && firstId != null && firstId === previousFirstId) {
      break;
    }
    previousFirstId = firstId;
    out.push(...batch);
    pages += 1;
    if (batch.length < limit || pages >= maxPages) {
      break;
    }
    offset += limit;
  }
  return out;
}

function fineractAuthHeaders(): { base: string; headers: Record<string, string> } {
  const base = (process.env.OMNL_FINERACT_BASE_URL || '').replace(/\/$/, '');
  const tenant = process.env.OMNL_FINERACT_TENANT || 'omnl';
  const user =
    process.env.OMNL_FINERACT_USER?.trim() ||
    process.env.OMNL_FINERACT_USERNAME?.trim() ||
    'app.omnl';
  const pass = process.env.OMNL_FINERACT_PASSWORD || '';
  if (!base || !pass) {
    throw new Error('OMNL_FINERACT_BASE_URL and OMNL_FINERACT_PASSWORD required for Fineract GL sync');
  }
  const auth = Buffer.from(`${user}:${pass}`).toString('base64');
  return {
    base,
    headers: {
      Authorization: `Basic ${auth}`,
      'Fineract-Platform-TenantId': tenant,
      'Content-Type': 'application/json',
      Accept: 'application/json',
    },
  };
}

function parseGlAccountBatch(
  data: FineractGlAccountRow[] | { pageItems?: FineractGlAccountRow[] },
): FineractGlAccountRow[] {
  if (Array.isArray(data)) return data;
  return data.pageItems ?? [];
}

/** Fetch GL accounts by code (fast path for HO nostro/reserve audit). */
export async function fetchFineractGlAccountsByCodes(glCodes: string[]): Promise<FineractGlAccountRow[]> {
  const unique = [...new Set(glCodes.map((c) => c.trim()).filter(Boolean))];
  if (unique.length === 0) return [];
  const { base, headers } = fineractAuthHeaders();
  const out: FineractGlAccountRow[] = [];
  const seen = new Set<number>();
  for (const glCode of unique) {
    const { data } = await axios.get<
      FineractGlAccountRow[] | { pageItems?: FineractGlAccountRow[] }
    >(`${base}/glaccounts`, {
      headers,
      timeout: 25000,
      params: { glCode, limit: 50 },
    });
    for (const row of parseGlAccountBatch(data)) {
      if (row.id != null && !seen.has(row.id)) {
        out.push(row);
        seen.add(row.id);
      }
    }
  }
  return out;
}

export function compareRegistryToFineract(
  registry: IpsasGlRegistry,
  fineractRows: FineractGlAccountRow[]
): {
  registryCodes: string[];
  fineractCodes: string[];
  missingInFineract: string[];
  aligned: boolean;
} {
  const registryCodes = registry.accounts.map((a) => a.glCode).sort();
  const fineractCodes = [
    ...new Set(
      fineractRows
        .map((r) => (r.glCode != null ? String(r.glCode).trim() : ''))
        .filter(Boolean)
    ),
  ].sort();
  const fs = new Set(fineractCodes);
  const missingInFineract = registryCodes.filter((c) => !fs.has(c));
  return {
    registryCodes,
    fineractCodes,
    missingInFineract,
    aligned: missingInFineract.length === 0,
  };
}

/** Lightweight Fineract reachability (GET `/glaccounts?limit=1`). */
export async function checkFineractConnectivity(): Promise<{
  ok: boolean;
  statusCode?: number;
  message: string;
}> {
  const base = (process.env.OMNL_FINERACT_BASE_URL || '').replace(/\/$/, '');
  const tenant = process.env.OMNL_FINERACT_TENANT || 'omnl';
  const user =
    process.env.OMNL_FINERACT_USER?.trim() ||
    process.env.OMNL_FINERACT_USERNAME?.trim() ||
    'app.omnl';
  const pass = process.env.OMNL_FINERACT_PASSWORD || '';
  if (!base || !pass) {
    return { ok: false, message: 'OMNL_FINERACT_BASE_URL and OMNL_FINERACT_PASSWORD required' };
  }
  const auth = Buffer.from(`${user}:${pass}`).toString('base64');
  const headers: Record<string, string> = {
    Authorization: `Basic ${auth}`,
    'Fineract-Platform-TenantId': tenant,
    'Content-Type': 'application/json',
  };
  try {
    const url = `${base}/glaccounts?limit=1&offset=0`;
    const { status } = await axios.get(url, {
      headers,
      timeout: 20000,
      validateStatus: () => true,
    });
    const ok = status >= 200 && status < 500;
    return {
      ok,
      statusCode: status,
      message: ok ? 'Fineract API reachable' : `Unexpected HTTP ${status}`,
    };
  } catch (e) {
    const msg = e instanceof Error ? e.message : String(e);
    return { ok: false, message: msg };
  }
}
