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

use PDO;
use Gestoreasy\Core\Fiscal\Sped\BlockInterface;
use Gestoreasy\Core\Fiscal\Sped\Period;
use Gestoreasy\Core\Fiscal\Sped\Validation\SpedValidator;
use Gestoreasy\Core\Fiscal\TaxConfig;

class BlockC implements BlockInterface {
    public function id(): string { return 'C'; }

    public function generate(PDO $pdo, Period $period, array $options = []): array {
        $lines = [];
        // C001 - Abertura do Bloco C (ind_mov: 0=com movimento)
        $lines[] = '|C001|0|';

        // Resolve default tax rates (ICMS/PIS/COFINS/IPI/ICMS ST)
        $rates = $this->resolveRates($pdo, $options);

        // Vendas (saídas) → sales_orders (C100/C170)
        foreach ($this->salesOrders($pdo, $period) as $so) {
            $lines[] = $this->formatC100([
                'ind_oper' => 1, // 1=Saída
                'ind_emit' => 0, // 0=Emissão própria
                'cod_mod' => '55',
                'cod_sit' => '00',
                'serie' => '',
                'num_doc' => (string)($so['numero'] ?? ''),
                'chv_nfe' => '',
                'dt_doc' => $this->ymd((string)($so['created_at'] ?? '')),
                'dt_e_s' => $this->ymd((string)($so['created_at'] ?? '')),
                'vl_doc' => (float)($so['valor_total'] ?? 0),
                'ind_pgto' => 0,
                'vl_desc' => 0,
                'vl_merc' => (float)($so['valor_bruto'] ?? (float)($so['valor_total'] ?? 0)),
                // Demais campos não mapeados ficam vazios
            ]);
            $itemN = 0;
            $agg = [];
            foreach ($this->salesOrderItems($pdo, (int)$so['id']) as $it) {
                $itemN++;
                $c170 = $this->formatC170($pdo, [
                    'num_item' => $itemN,
                    'product_id' => (int)$it['product_id'],
                    'quantidade' => (float)$it['quantidade'],
                    'vl_item' => (float)$it['total'],
                    'ind_oper' => 1,
                    'rates' => $rates,
                ]);
                $lines[] = $c170['line'];
                // agregar por (cst, cfop)
                $key = $c170['cst'].'|'.$c170['cfop'];
                if (!isset($agg[$key])) { $agg[$key] = ['cst' => $c170['cst'], 'cfop' => $c170['cfop'], 'aliq_icms' => $c170['aliq_icms'], 'vl_opr' => 0.0, 'vl_bc_icms' => 0.0, 'vl_icms' => 0.0, 'vl_ipi' => 0.0, 'vl_bc_icms_st' => 0.0, 'vl_icms_st' => 0.0]; }
                $agg[$key]['vl_opr'] += (float)$it['total'];
                $agg[$key]['vl_bc_icms'] += $c170['vl_bc_icms'];
                $agg[$key]['vl_icms'] += $c170['vl_icms'];
                $agg[$key]['vl_ipi'] += $c170['vl_ipi'];
                $agg[$key]['vl_bc_icms_st'] += $c170['vl_bc_icms_st'];
                $agg[$key]['vl_icms_st'] += $c170['vl_icms_st'];
            }
            // C190 por documento (consolidação)
            foreach ($agg as $g) { $lines[] = $this->formatC190($g['cst'], $g['cfop'], (float)$g['aliq_icms'], (float)$g['vl_opr'], (float)$g['vl_bc_icms'], (float)$g['vl_icms'], (float)$g['vl_bc_icms_st'], (float)$g['vl_icms_st'], (float)$g['vl_ipi']); }
        }

        // Compras (entradas) → purchase_invoices (C100/C170)
        foreach ($this->purchaseInvoices($pdo, $period) as $pi) {
            $lines[] = $this->formatC100([
                'ind_oper' => 0, // 0=Entrada
                'ind_emit' => 1, // 1=Terceiros
                'cod_mod' => '55',
                'cod_sit' => '00',
                'serie' => '',
                'num_doc' => (string)($pi['numero'] ?? ''),
                'chv_nfe' => '',
                'dt_doc' => $this->ymd((string)($pi['data_emissao'] ?? '')),
                'dt_e_s' => $this->ymd((string)($pi['data_emissao'] ?? '')),
                'vl_doc' => (float)($pi['valor'] ?? 0),
                'ind_pgto' => 0,
                'vl_desc' => 0,
                'vl_merc' => (float)($pi['valor'] ?? 0),
            ]);
            $itemN = 0;
            $agg = [];
            foreach ($this->purchaseInvoiceItems($pdo, (int)$pi['id']) as $it) {
                $itemN++;
                $c170 = $this->formatC170($pdo, [
                    'num_item' => $itemN,
                    'product_id' => (int)$it['product_id'],
                    'quantidade' => (float)$it['quantidade'],
                    'vl_item' => (float)$it['total'],
                    'ind_oper' => 0,
                    'rates' => $rates,
                ]);
                $lines[] = $c170['line'];
                $key = $c170['cst'].'|'.$c170['cfop'];
                if (!isset($agg[$key])) { $agg[$key] = ['cst' => $c170['cst'], 'cfop' => $c170['cfop'], 'aliq_icms' => $c170['aliq_icms'], 'vl_opr' => 0.0, 'vl_bc_icms' => 0.0, 'vl_icms' => 0.0, 'vl_ipi' => 0.0, 'vl_bc_icms_st' => 0.0, 'vl_icms_st' => 0.0]; }
                $agg[$key]['vl_opr'] += (float)$it['total'];
                $agg[$key]['vl_bc_icms'] += $c170['vl_bc_icms'];
                $agg[$key]['vl_icms'] += $c170['vl_icms'];
                $agg[$key]['vl_ipi'] += $c170['vl_ipi'];
                $agg[$key]['vl_bc_icms_st'] += $c170['vl_bc_icms_st'];
                $agg[$key]['vl_icms_st'] += $c170['vl_icms_st'];
            }
            foreach ($agg as $g) { $lines[] = $this->formatC190($g['cst'], $g['cfop'], (float)$g['aliq_icms'], (float)$g['vl_opr'], (float)$g['vl_bc_icms'], (float)$g['vl_icms'], (float)$g['vl_bc_icms_st'], (float)$g['vl_icms_st'], (float)$g['vl_ipi']); }
        }

        // C990 - Encerramento do Bloco C
        $qtd_lin_c = count($lines) + 1; // incluir C990
        $lines[] = sprintf('|C990|%d|', $qtd_lin_c);
        return $lines;
    }

    private function salesOrders(PDO $pdo, Period $p): array {
        $items = [];
        try {
            $stmt = $pdo->prepare('SELECT id, numero, cliente_id, valor_total, created_at FROM sales_orders WHERE DATE(created_at) BETWEEN ? AND ? ORDER BY id ASC');
            $stmt->execute([$p->startDate(), $p->endDate()]);
            $rows = $stmt->fetchAll();
            // calcular valor_bruto (soma itens) quando possível
            foreach ($rows as &$r) {
                try {
                    $sum = $pdo->prepare('SELECT SUM(total) AS s FROM sales_order_items WHERE order_id = ?');
                    $sum->execute([(int)($r['id'] ?? 0)]);
                    $gross = $sum->fetch()['s'] ?? null;
                    $r['valor_bruto'] = $gross !== null ? (float)$gross : (float)($r['valor_total'] ?? 0);
                } catch (\Throwable $e) {
                    $r['valor_bruto'] = (float)($r['valor_total'] ?? 0);
                }
                $items[] = $r;
            }
        } catch (\Throwable $e) {}
        return $items;
    }

    private function salesOrderItems(PDO $pdo, int $orderId): array {
        try {
            $stmt = $pdo->prepare('SELECT product_id, quantidade, preco, total FROM sales_order_items WHERE order_id = ? ORDER BY id ASC');
            $stmt->execute([$orderId]);
            return $stmt->fetchAll();
        } catch (\Throwable $e) { return []; }
    }

    private function purchaseInvoices(PDO $pdo, Period $p): array {
        try {
            $stmt = $pdo->prepare('SELECT id, numero, fornecedor_id, valor, data_emissao FROM purchase_invoices WHERE DATE(data_emissao) BETWEEN ? AND ? ORDER BY id ASC');
            $stmt->execute([$p->startDate(), $p->endDate()]);
            return $stmt->fetchAll();
        } catch (\Throwable $e) { return []; }
    }

    private function purchaseInvoiceItems(PDO $pdo, int $invoiceId): array {
        try {
            $stmt = $pdo->prepare('SELECT product_id, quantidade, preco, total FROM purchase_invoice_items WHERE invoice_id = ? ORDER BY id ASC');
            $stmt->execute([$invoiceId]);
            return $stmt->fetchAll();
        } catch (\Throwable $e) { return []; }
    }

    private function formatC100(array $d): string {
        // |C100|IND_OPER|IND_EMIT|COD_MOD|COD_SIT|SER|NUM_DOC|CHV_NFE|DT_DOC|DT_E_S|VL_DOC|IND_PGTO|VL_DESC|VL_ABAT_NT|VL_MERC|IND_FRT|VL_FRT|VL_SEG|VL_OUT_DA|VL_BC_ICMS|VL_ICMS|VL_BC_ICMS_ST|VL_ICMS_ST|VL_IPI|VL_PIS|VL_COFINS|VL_PIS_ST|VL_COFINS_ST|
        return sprintf('|C100|%d|%d|%s|%s|%s|%s|%s|%s|%s|%.2f|%d|%.2f|%s|%.2f|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|',
            (int)($d['ind_oper'] ?? 0), (int)($d['ind_emit'] ?? 0), (string)($d['cod_mod'] ?? ''), (string)($d['cod_sit'] ?? ''),
            (string)($d['serie'] ?? ''), (string)($d['num_doc'] ?? ''), (string)($d['chv_nfe'] ?? ''), (string)($d['dt_doc'] ?? ''), (string)($d['dt_e_s'] ?? ''),
            (float)($d['vl_doc'] ?? 0), (int)($d['ind_pgto'] ?? 0), (float)($d['vl_desc'] ?? 0), '', (float)($d['vl_merc'] ?? 0), '', '', '', '', '', '', '', '', '', '', '', ''
        );
    }

    private function formatC170(PDO $pdo, array $d): array {
        $prod = $this->productById($pdo, (int)($d['product_id'] ?? 0));
        $cod = (string)($prod['codigo'] ?? '');
        $desc = (string)($prod['descricao'] ?? '');
        $un = (string)($prod['unidade'] ?? '');
        // regras simples de CFOP/CST
        $indOper = (int)($d['ind_oper'] ?? 1); // 1=Saída, 0=Entrada
        $cfop = $indOper === 1 ? '5102' : '1102';
        $cstIcms = '000';
        $ratesDefault = is_array($d['rates'] ?? null) ? $d['rates'] : ['icms' => 0.18, 'pis' => 0.0165, 'cofins' => 0.076, 'ipi' => 0.10, 'icms_st' => 0.0];
        // aplicar regras por NCM/UF/operação quando disponíveis
        $company = $this->loadCompany($pdo);
        $uf = strtoupper((string)($company['estado'] ?? ''));
        $rates = $this->resolveRatesForProduct($pdo, $ratesDefault, $prod, $uf, $indOper);
        $vlItem = (float)($d['vl_item'] ?? 0);
        $vlBcIcms = $vlItem;
        $aliqIcmsPerc = round($rates['icms'] * 100, 2);
        $vlIcms = round($vlBcIcms * $rates['icms'], 2);
        $cstPis = '01'; $vlBcPis = $vlItem; $aliqPisPerc = round($rates['pis'] * 100, 2); $vlPis = round($vlBcPis * $rates['pis'], 2);
        $cstCofins = '01'; $vlBcCof = $vlItem; $aliqCofPerc = round($rates['cofins'] * 100, 2); $vlCof = round($vlBcCof * $rates['cofins'], 2);
        // IPI (básico): base = vl_item, cst_ipi = '50', cod_enq = '999'
        $cstIpi = '50'; $codEnq = '999'; $vlBcIpi = $vlItem; $aliqIpiPerc = round(($rates['ipi'] ?? 0) * 100, 2); $vlIpi = round($vlBcIpi * ($rates['ipi'] ?? 0), 2);
        // ICMS ST (básico): quando taxa fornecida
        $vlBcIcmsSt = $vlItem; $aliqStPerc = round(($rates['icms_st'] ?? 0) * 100, 2); $vlIcmsSt = round($vlBcIcmsSt * ($rates['icms_st'] ?? 0), 2);
        // |C170|NUM_ITEM|COD_ITEM|DESCR_COMPL|QTD|UNID|VL_ITEM|IND_MOV|CST_ICMS|CFOP|COD_NAT|VL_BC_ICMS|ALIQ_ICMS|VL_ICMS|VL_BC_ICMS_ST|ALIQ_ST|VL_ICMS_ST|IND_APUR|CST_IPI|COD_ENQ|VL_BC_IPI|ALIQ_IPI|VL_IPI|CST_PIS|VL_BC_PIS|ALIQ_PIS|VL_PIS|CST_COFINS|VL_BC_COFINS|ALIQ_COFINS|VL_COFINS|COD_CTA|VL_ABAT_NT|
        $line = sprintf('|C170|%d|%s|%s|%.3f|%s|%.2f|0|%s|%s|%s|%.2f|%.2f|%.2f|%.2f|%.2f|%.2f|%s|%s|%s|%.2f|%.2f|%.2f|%s|%.2f|%.2f|%.2f|%s|%.2f|%.2f|%s|%s|',
            (int)($d['num_item'] ?? 0), $cod, $desc, (float)($d['quantidade'] ?? 0), $un, $vlItem, $cstIcms, $cfop, '',
            $vlBcIcms, $aliqIcmsPerc, $vlIcms, $vlBcIcmsSt, $aliqStPerc, $vlIcmsSt, '', $cstIpi, $codEnq, $vlBcIpi, $aliqIpiPerc, $vlIpi,
            $cstPis, $vlBcPis, $aliqPisPerc, $vlPis, $cstCofins, $vlBcCof, $aliqCofPerc, $vlCof, '', ''
        );
        return ['line' => $line, 'cst' => $cstIcms, 'cfop' => $cfop, 'aliq_icms' => $aliqIcmsPerc, 'vl_bc_icms' => $vlBcIcms, 'vl_icms' => $vlIcms, 'vl_ipi' => $vlIpi, 'vl_bc_icms_st' => $vlBcIcmsSt, 'vl_icms_st' => $vlIcmsSt];
    }

    private function formatC190(string $cst, string $cfop, float $aliqIcms, float $vlOpr, float $vlBcIcms, float $vlIcms, float $vlBcIcmsSt, float $vlIcmsSt, float $vlIpi): string {
        // |C190|CST_ICMS|CFOP|ALIQ_ICMS|VL_OPR|VL_BC_ICMS|VL_ICMS|VL_BC_ICMS_ST|VL_ICMS_ST|VL_RED_BC|VL_IPI|COD_OBS|
        return sprintf('|C190|%s|%s|%.2f|%.2f|%.2f|%.2f|%.2f|%.2f|%.2f|%.2f|%s|',
            $cst, $cfop, $aliqIcms, $vlOpr, $vlBcIcms, $vlIcms, $vlBcIcmsSt, $vlIcmsSt, 0.0, $vlIpi, ''
        );
    }

    private function productById(PDO $pdo, int $id): array {
        if ($id <= 0) return [];
        try {
            // Incluir NCM para permitir resolução de alíquotas por prefixo nas regras fiscais
            $st = $pdo->prepare('SELECT codigo, descricao, unidade, ncm FROM products WHERE id = ?');
            $st->execute([$id]);
            $r = $st->fetch();
            return $r ?: [];
        } catch (\Throwable $e) { return []; }
    }

    private function ymd(string $date): string {
        if ($date === '') return '';
        // Accept formats like 'YYYY-MM-DD' or full 'YYYY-MM-DD HH:MM:SS'
        $d = substr($date, 0, 10);
        return str_replace('-', '', $d);
    }

    private function resolveRates(PDO $pdo, array $options): array {
        $icms = isset($options['icms_rate']) ? (float)$options['icms_rate'] : null;
        $pis = isset($options['pis_rate']) ? (float)$options['pis_rate'] : null;
        $cofins = isset($options['cofins_rate']) ? (float)$options['cofins_rate'] : null;
        $ipi = isset($options['ipi_rate']) ? (float)$options['ipi_rate'] : null;
        $icmsSt = isset($options['icms_st_rate']) ? (float)$options['icms_st_rate'] : null;
        if ($icms !== null && $icms > 0 && $pis !== null && $pis > 0 && $cofins !== null && $cofins > 0) {
            return ['icms' => $icms, 'pis' => $pis, 'cofins' => $cofins, 'ipi' => ($ipi ?? 0.10), 'icms_st' => ($icmsSt ?? 0.0)];
        }
        try {
            $cfg = TaxConfig::get($pdo);
            return [
                'icms' => (float)($cfg['options']['icms_default_rate'] ?? ($icms ?? 0.18)),
                'pis' => (float)($cfg['options']['pis_default_rate'] ?? ($pis ?? 0.0165)),
                'cofins' => (float)($cfg['options']['cofins_default_rate'] ?? ($cofins ?? 0.076)),
                'ipi' => (float)($cfg['options']['ipi_default_rate'] ?? ($ipi ?? 0.10)),
                'icms_st' => (float)($cfg['options']['icms_st_default_rate'] ?? ($icmsSt ?? 0.0)),
            ];
        } catch (\Throwable $e) {
            return ['icms' => ($icms ?? 0.18), 'pis' => ($pis ?? 0.0165), 'cofins' => ($cofins ?? 0.076), 'ipi' => ($ipi ?? 0.10), 'icms_st' => ($icmsSt ?? 0.0)];
        }
    }

    private function resolveRatesForProduct(PDO $pdo, array $ratesDefault, array $prod, string $uf, int $indOper): array {
        // Estrutura esperada em TaxConfig.options.tax_rules:
        // [ { ncm_prefix: "0000", uf: "RJ", op: "saida"|"entrada", icms: 0.12, pis: 0.0165, cofins: 0.076, ipi: 0.10, icms_st: 0.0 }, ... ]
        try {
            $cfg = TaxConfig::get($pdo);
            $rules = is_array($cfg['options']['tax_rules'] ?? null) ? $cfg['options']['tax_rules'] : [];
            $ncm = (string)($prod['ncm'] ?? '');
            foreach ($rules as $r) {
                $prefix = (string)($r['ncm_prefix'] ?? '');
                $ruleUf = strtoupper((string)($r['uf'] ?? ''));
                $op = strtolower((string)($r['op'] ?? ''));
                if ($prefix !== '' && str_starts_with($ncm, $prefix) && ($ruleUf === '' || $ruleUf === strtoupper($uf)) &&
                    (($indOper === 1 && $op === 'saida') || ($indOper === 0 && $op === 'entrada') || $op === '')) {
                    $icms = isset($r['icms']) ? (float)$r['icms'] : $ratesDefault['icms'];
                    $pis = isset($r['pis']) ? (float)$r['pis'] : $ratesDefault['pis'];
                    $cofins = isset($r['cofins']) ? (float)$r['cofins'] : $ratesDefault['cofins'];
                    $ipi = isset($r['ipi']) ? (float)$r['ipi'] : ($ratesDefault['ipi'] ?? 0.10);
                    $icmsSt = isset($r['icms_st']) ? (float)$r['icms_st'] : ($ratesDefault['icms_st'] ?? 0.0);
                    return ['icms' => $icms, 'pis' => $pis, 'cofins' => $cofins, 'ipi' => $ipi, 'icms_st' => $icmsSt];
                }
            }
        } catch (\Throwable $e) {}
        return $ratesDefault;
    }

    private function loadCompany(PDO $pdo): array {
        try { $st = $pdo->query('SELECT * FROM empresas ORDER BY id ASC LIMIT 1'); $r = $st->fetch(); return $r ?: []; } catch (\Throwable $e) { return []; }
    }
}