<?php
namespace Gestoreasy\Core\Fiscal\Sped\Validation;

class SpedValidator {
    public static function digitsOnly(string $s): string { return preg_replace('/\D+/', '', $s); }
    public static function validateCNPJ(string $cnpj): bool { $d = self::digitsOnly($cnpj); return strlen($d) === 14; }
    public static function validateCPF(string $cpf): bool { $d = self::digitsOnly($cpf); return strlen($d) === 11; }
    public static function validateUF(string $uf): bool { return (bool)preg_match('/^[A-Z]{2}$/', strtoupper($uf)); }
    public static function notEmpty($v): bool { return isset($v) && trim((string)$v) !== ''; }

    /**
     * Validate SPED Fiscal lines with cross-checks and basic consistency.
     * Returns a list of textual issues; empty list means no issues found.
     */
    public static function validateSpedLines(array $lines): array {
        $issues = [];
        $last = $lines[count($lines)-1] ?? '';
        if (!is_string($last) || !str_starts_with($last, '|9999|')) {
            $issues[] = 'Arquivo não encerra com |9999|';
        }
        // Presence of key open/close records
        if (!in_array('|C001|0|', $lines, true)) { $issues[] = 'Bloco C sem abertura C001'; }
        if (!self::containsPrefix($lines, '|C990|')) { $issues[] = 'Bloco C sem encerramento C990'; }
        if (!in_array('|E001|0|', $lines, true)) { $issues[] = 'Bloco E sem abertura E001'; }
        if (!self::containsPrefix($lines, '|E990|')) { $issues[] = 'Bloco E sem encerramento E990'; }
        if (!in_array('|1001|1|', $lines, true)) { $issues[] = 'Bloco 1 sem abertura 1001'; }
        if (!self::containsPrefix($lines, '|1990|')) { $issues[] = 'Bloco 1 sem encerramento 1990'; }

        // E110 internal formula consistency
        $e110 = self::firstByPrefix($lines, '|E110|');
        if ($e110 !== null) {
            $nums = self::parseNumericFields($e110);
            if (count($nums) >= 12) {
                [$vl_tot_debitos, $vl_aj_debitos, $vl_tot_creditos, $vl_aj_creditos, $vl_estornos_deb, $vl_estornos_cred, $vl_sld_credor_ant, $vl_sld_apurado, $vl_tot_ded, $vl_icms_recolher, $vl_sld_credor_transportar, $deb_esp] = array_slice($nums, 0, 12);
                $calc = round($vl_sld_credor_ant + $vl_tot_debitos - $vl_tot_creditos - $vl_aj_creditos + $vl_aj_debitos - $vl_tot_ded, 2);
                if (abs($calc - $vl_sld_apurado) > 0.01) {
                    $issues[] = 'E110 VL_SLD_APURADO não confere com fórmula interna';
                }
                if ($vl_sld_apurado >= 0 && abs($vl_icms_recolher - $vl_sld_apurado) > 0.01) {
                    $issues[] = 'E110 VL_ICMS_RECOLHER não confere com saldo apurado';
                }
                if ($vl_sld_apurado < 0 && abs($vl_sld_credor_transportar - abs($vl_sld_apurado)) > 0.01) {
                    $issues[] = 'E110 VL_SLD_CREDOR_TRANSPORTAR não confere com saldo apurado';
                }
            } else {
                $issues[] = 'E110 com campos insuficientes';
            }
        } else {
            $issues[] = 'E110 ausente';
        }

        // C190 internal: VL_ICMS consistent with base and aliquot
        foreach ($lines as $l) {
            if (str_starts_with($l, '|C190|')) {
                $parts = explode('|', trim($l));
                // Expect: |C190|CST|CFOP|ALIQ_ICMS|VL_OPR|VL_BC_ICMS|VL_ICMS|VL_BC_ICMS_ST|VL_ICMS_ST|VL_RED_BC|VL_IPI|COD_OBS|
                if (count($parts) >= 13) {
                    $aliq = (float)($parts[4] ?? 0); // percent
                    $vl_bc_icms = (float)($parts[6] ?? 0);
                    $vl_icms = (float)($parts[7] ?? 0);
                    $expected = round($vl_bc_icms * ($aliq/100.0), 2);
                    if ($vl_icms - $expected > 0.01) {
                        $issues[] = 'C190 VL_ICMS superior ao esperado pela alíquota';
                    }
                    if ($expected - $vl_icms > 0.01) {
                        $issues[] = 'C190 VL_ICMS inferior ao esperado pela alíquota';
                    }
                    if ($vl_icms < -0.001 || $vl_bc_icms < -0.001) { $issues[] = 'C190 valores negativos não permitidos'; }
                } else {
                    $issues[] = 'C190 com campos insuficientes';
                }
            }
        }

        // D190 internal: VL_ICMS consistent with base and aliquot
        foreach ($lines as $l) {
            if (str_starts_with($l, '|D190|')) {
                $parts = explode('|', trim($l));
                // Expect: |D190|CST|CFOP|ALIQ_ICMS|VL_OPR|VL_BC_ICMS|VL_ICMS|VL_BC_ICMS_ST|VL_ICMS_ST|VL_RED_BC|VL_PIS|VL_COFINS|COD_OBS|
                if (count($parts) >= 13) {
                    // Basic CFOP/CST format checks
                    $cst = (string)($parts[2] ?? '');
                    $cfop = (string)($parts[3] ?? '');
                    if ($cst !== '' && !preg_match('/^\d{2}$/', $cst)) {
                        $issues[] = 'D190 CST inválido (deve conter 2 dígitos)';
                    }
                    if ($cfop !== '' && !preg_match('/^\d{4}$/', $cfop)) {
                        $issues[] = 'D190 CFOP inválido (deve conter 4 dígitos)';
                    }
                    $aliq = (float)($parts[4] ?? 0); // percent
                    $vl_bc_icms = (float)($parts[6] ?? 0);
                    $vl_icms = (float)($parts[7] ?? 0);
                    $expected = round($vl_bc_icms * ($aliq/100.0), 2);
                    if ($vl_icms - $expected > 0.01) {
                        $issues[] = 'D190 VL_ICMS superior ao esperado pela alíquota';
                    }
                    if ($expected - $vl_icms > 0.01) {
                        $issues[] = 'D190 VL_ICMS inferior ao esperado pela alíquota';
                    }
                    if ($vl_icms < -0.001 || $vl_bc_icms < -0.001) { $issues[] = 'D190 valores negativos não permitidos'; }
                    // CST compatibility: isentas/suspensas devem ter base/ICMS zerados
                    if (in_array($cst, ['40','41','50'], true)) {
                        if ($vl_bc_icms > 0.001 || $vl_icms > 0.001) {
                            $issues[] = 'D190 CST isento/suspenso exige base/ICMS zero';
                        }
                    }
                } else {
                    $issues[] = 'D190 com campos insuficientes';
                }
            }
        }

        // C170 consistency checks: IPI and PIS/COFINS (with small rounding tolerance)
        foreach ($lines as $l) {
            if (str_starts_with($l, '|C170|')) {
                $p = explode('|', trim($l));
                // IPI: VL_IPI == VL_BC_IPI * (ALIQ_IPI/100)
                if (count($p) >= 24) {
                    $vl_bc_ipi = (float)($p[21] ?? 0);
                    $aliq_ipi = (float)($p[22] ?? 0); // percent
                    $vl_ipi = (float)($p[23] ?? 0);
                    $expIpi = round($vl_bc_ipi * ($aliq_ipi/100.0), 2);
                    if (abs($vl_ipi - $expIpi) > 0.02) {
                        $issues[] = 'C170 VL_IPI não confere com base/alíquota';
                    }
                }
                // PIS: VL_PIS == VL_BC_PIS * (ALIQ_PIS/100)
                if (count($p) >= 28) {
                    // Indexes after splitting with leading '|': 24=CST_PIS, 25=VL_BC_PIS, 26=ALIQ_PIS, 27=VL_PIS
                    $vl_bc_pis = (float)($p[25] ?? 0);
                    $aliq_pis = (float)($p[26] ?? 0);
                    $vl_pis = (float)($p[27] ?? 0);
                    $expPis = round($vl_bc_pis * ($aliq_pis/100.0), 2);
                    if (abs($vl_pis - $expPis) > 0.02) {
                        $issues[] = 'C170 VL_PIS não confere com base/alíquota';
                    }
                }
                // COFINS: VL_COFINS == VL_BC_COFINS * (ALIQ_COFINS/100)
                if (count($p) >= 32) {
                    // Indexes: 28=CST_COFINS, 29=VL_BC_COFINS, 30=ALIQ_COFINS, 31=VL_COFINS
                    $vl_bc_cof = (float)($p[29] ?? 0);
                    $aliq_cof = (float)($p[30] ?? 0);
                    $vl_cof = (float)($p[31] ?? 0);
                    $expCof = round($vl_bc_cof * ($aliq_cof/100.0), 2);
                    if (abs($vl_cof - $expCof) > 0.02) {
                        $issues[] = 'C170 VL_COFINS não confere com base/alíquota';
                    }
                }
            }
        }

        // Per-document cross-check: sum VL_ITEM by (CST,CFOP) equals C190 VL_OPR
        $currentDocAgg = [];
        $inDoc = false;
        foreach ($lines as $l) {
            if (str_starts_with($l, '|C100|')) {
                $inDoc = true; $currentDocAgg = [];
                continue;
            }
            if ($inDoc && str_starts_with($l, '|C170|')) {
                $p = explode('|', trim($l));
                if (count($p) >= 11) {
                    $cst = (string)($p[9] ?? '');
                    $cfop = (string)($p[10] ?? '');
                    $vl_item = (float)($p[7] ?? 0);
                    $key = $cst.'|'.$cfop;
                    if (!isset($currentDocAgg[$key])) $currentDocAgg[$key] = 0.0;
                    $currentDocAgg[$key] += $vl_item;
                }
            }
            if ($inDoc && str_starts_with($l, '|C190|')) {
                $p = explode('|', trim($l));
                if (count($p) >= 6) {
                    $cst = (string)($p[2] ?? '');
                    $cfop = (string)($p[3] ?? '');
                    $vl_opr = (float)($p[5] ?? 0);
                    $key = $cst.'|'.$cfop;
                    $sum = (float)($currentDocAgg[$key] ?? 0);
                    if (abs($vl_opr - $sum) > 0.02) {
                        $issues[] = 'C190 VL_OPR não confere com soma de VL_ITEM do documento (CST/CFOP)';
                    }
                }
            }
        }
        return $issues;
    }

    private static function containsPrefix(array $lines, string $prefix): bool {
        foreach ($lines as $l) { if (is_string($l) && str_starts_with($l, $prefix)) return true; }
        return false;
    }
    private static function firstByPrefix(array $lines, string $prefix): ?string {
        foreach ($lines as $l) { if (is_string($l) && str_starts_with($l, $prefix)) return $l; }
        return null;
    }
    private static function parseNumericFields(string $line): array {
        $parts = explode('|', trim($line));
        $out = [];
        // Skip prefix and map sequential numeric fields
        for ($i=2; $i<count($parts); $i++) {
            $v = $parts[$i]; if ($v === '') continue; $out[] = (float)$v;
        }
        return $out;
    }
}