<?php

require_once __DIR__ . '/../../core/database.php';
require_once __DIR__ . '/BillingReceivablesService.php';
require_once __DIR__ . '/BillingRuntimeGate.php';

class BillingCustomerAgingService
{
    private $pdo;

    public function __construct(?PDO $pdo = null)
    {
        $this->pdo = $pdo ?: db();
    }

    public function summary(array $filters = array())
    {
        $environment = $this->normalizeEnvironment($filters['environment'] ?? 'sandbox');
        billing_assert_runtime_operation('read', $environment, 'Aging de clientes');
        $rows = $this->openRows($filters);
        $groups = $this->groupRows($rows);
        return array(
            'environment' => $environment,
            'rows' => array_values($groups),
            'kpis' => $this->kpis($groups),
            'centers' => (new BillingReceivablesService($this->pdo))->listIncomeCenters(),
        );
    }

    public function detail($customerKey, array $filters = array())
    {
        $customerKey = trim((string)$customerKey);
        $environment = $this->normalizeEnvironment($filters['environment'] ?? 'sandbox');
        billing_assert_runtime_operation('read', $environment, 'Detalle de aging de cliente');
        if ($customerKey === '') throw new Exception('Cliente requerido.');
        $rows = $this->openRows($filters);
        $selected = array_values(array_filter($rows, function($row) use ($customerKey){ return $this->customerKey($row) === $customerKey; }));
        usort($selected, function($a,$b){
            $da = $a['aging_due_date'] ?: '9999-12-31';
            $db = $b['aging_due_date'] ?: '9999-12-31';
            return $da === $db ? strcmp((string)$a['document_number'], (string)$b['document_number']) : strcmp($da,$db);
        });
        $groups = $this->groupRows($selected);
        return array(
            'environment' => $this->normalizeEnvironment($filters['environment'] ?? 'sandbox'),
            'customer' => $groups[$customerKey] ?? null,
            'rows' => $selected,
        );
    }

    private function openRows(array $filters)
    {
        $environment = $this->normalizeEnvironment($filters['environment'] ?? 'sandbox');
        $search = trim((string)($filters['search'] ?? ''));
        $bucket = trim((string)($filters['bucket'] ?? 'all'));
        $centerId = trim((string)($filters['center_id'] ?? 'all'));

        $service = new BillingReceivablesService($this->pdo);
        $result = $service->listReceivables(array(
            'environment' => $environment,
            'status' => 'open',
            'search' => $search,
            'limit' => 5000,
        ));

        $rows = array();
        foreach ($result['rows'] as $row) {
            // Aging comercial en neto. El saldo bruto permanece disponible en
            // balance_amount para registrar y conciliar los cobros reales.
            $balance = max((float)($row['net_balance_amount'] ?? 0), 0.0);
            if ($balance <= 0.5) continue;
            $dueDate = $row['due_date'] ?: $row['payment_due_date'] ?: $row['issue_date'];
            $daysOverdue = $this->daysOverdue($dueDate);
            $daysToDue = $this->daysToDue($dueDate);
            $row['aging_balance'] = $balance;
            $row['aging_due_date'] = $dueDate;
            $row['days_overdue'] = $daysOverdue;
            $row['days_to_due'] = $daysToDue;

            $effectiveCenter = (string)($row['effective_finance_center_id'] ?? '');
            if ($centerId !== '' && $centerId !== 'all') {
                if ($centerId === 'none' && $effectiveCenter !== '') continue;
                if ($centerId !== 'none' && $effectiveCenter !== (string)(int)$centerId) continue;
            }
            if (!$this->matchesBucket($row, $bucket)) continue;
            $rows[] = $row;
        }
        return $rows;
    }

    private function groupRows(array $rows)
    {
        $groups = array();
        foreach ($rows as $row) {
            $key = $this->customerKey($row);
            if (!isset($groups[$key])) {
                $groups[$key] = array(
                    'customer_key' => $key,
                    'customer_id' => (int)($row['crm_customer_id'] ?? 0),
                    'customer_name' => $this->first(array($row['customer_name'] ?? '', $row['receiver_legal_name'] ?? '', 'Cliente sin vincular')),
                    'customer_tax_id' => $this->first(array($row['customer_tax_id'] ?? '', $row['receiver_tax_id_code'] ?? '', '-')),
                    'documents_count' => 0,
                    'open_balance' => 0.0,
                    'overdue_balance' => 0.0,
                    'current_balance' => 0.0,
                    'due_7_balance' => 0.0,
                    'bucket_1_30' => 0.0,
                    'bucket_31_60' => 0.0,
                    'bucket_61_90' => 0.0,
                    'bucket_90_plus' => 0.0,
                    'oldest_due_date' => null,
                    'next_due_date' => null,
                    'max_days_overdue' => 0,
                    'unlinked_customer' => empty($row['crm_customer_id']) ? 1 : 0,
                    'without_center_balance' => 0.0,
                    'center_names' => array(),
                    'finance_center_name' => 'Sin centro',
                );
            }
            $balance = (float)$row['aging_balance'];
            $daysOverdue = (int)$row['days_overdue'];
            $daysToDue = (int)$row['days_to_due'];
            $due = $row['aging_due_date'];
            $g =& $groups[$key];
            $g['documents_count']++;
            $g['open_balance'] += $balance;
            if ($daysOverdue > 0) {
                $g['overdue_balance'] += $balance;
                $g['max_days_overdue'] = max($g['max_days_overdue'], $daysOverdue);
                if ($daysOverdue <= 30) $g['bucket_1_30'] += $balance;
                elseif ($daysOverdue <= 60) $g['bucket_31_60'] += $balance;
                elseif ($daysOverdue <= 90) $g['bucket_61_90'] += $balance;
                else $g['bucket_90_plus'] += $balance;
            } else {
                $g['current_balance'] += $balance;
                if ($daysToDue >= 0 && $daysToDue <= 7) $g['due_7_balance'] += $balance;
            }
            if ($due) {
                if ($g['oldest_due_date'] === null || $due < $g['oldest_due_date']) $g['oldest_due_date'] = $due;
                if ($daysOverdue === 0 && ($g['next_due_date'] === null || $due < $g['next_due_date'])) $g['next_due_date'] = $due;
            }
            if (!empty($row['effective_finance_center_id'])) $g['center_names'][(string)$row['effective_finance_center_id']] = $row['finance_center_name'] ?: 'Centro';
            else $g['without_center_balance'] += $balance;
            unset($g);
        }

        foreach ($groups as &$group) {
            $count = count($group['center_names']);
            if ($count === 1 && $group['without_center_balance'] <= 0.5) $group['finance_center_name'] = reset($group['center_names']);
            elseif ($count > 0 && $group['without_center_balance'] > 0.5) $group['finance_center_name'] = 'Mixtos / sin centro';
            elseif ($count > 1) $group['finance_center_name'] = 'Múltiples centros';
            unset($group['center_names']);
        }
        unset($group);

        uasort($groups, function($a,$b){
            $x = (float)$b['overdue_balance'] <=> (float)$a['overdue_balance'];
            if ($x !== 0) return $x;
            $x = (float)$b['open_balance'] <=> (float)$a['open_balance'];
            return $x !== 0 ? $x : strcasecmp($a['customer_name'],$b['customer_name']);
        });
        return $groups;
    }

    private function kpis(array $groups)
    {
        $k = array('customers_count'=>count($groups),'open_balance'=>0.0,'overdue_balance'=>0.0,'current_balance'=>0.0,'due_7_balance'=>0.0,'documents_count'=>0,'unlinked_customers'=>0);
        foreach ($groups as $g) {
            foreach (array('open_balance','overdue_balance','current_balance','due_7_balance') as $f) $k[$f] += (float)$g[$f];
            $k['documents_count'] += (int)$g['documents_count'];
            if (!empty($g['unlinked_customer'])) $k['unlinked_customers']++;
        }
        return $k;
    }

    private function matchesBucket(array $row, $bucket)
    {
        if ($bucket === '' || $bucket === 'all') return true;
        if ($bucket === 'overdue') return (int)$row['days_overdue'] > 0;
        if ($bucket === 'current') return (int)$row['days_overdue'] === 0;
        if ($bucket === 'due_7') return (int)$row['days_overdue'] === 0 && (int)$row['days_to_due'] >= 0 && (int)$row['days_to_due'] <= 7;
        if ($bucket === 'no_customer') return empty($row['crm_customer_id']);
        if ($bucket === 'no_center') return empty($row['effective_finance_center_id']);
        return true;
    }

    private function customerKey(array $row)
    {
        if (!empty($row['crm_customer_id'])) return 'id:' . (int)$row['crm_customer_id'];
        $rut = strtoupper(preg_replace('/[^0-9K]/i','',(string)($row['receiver_tax_id_code'] ?? '')));
        if ($rut !== '') return 'rut:' . $rut;
        return 'name:' . md5(strtolower(trim((string)($row['receiver_legal_name'] ?? 'sin-cliente'))));
    }

    private function daysOverdue($date)
    {
        if (!$date) return 1;
        $today = new DateTime(date('Y-m-d'));
        $due = new DateTime($date);
        return $due < $today ? (int)$due->diff($today)->days : 0;
    }

    private function daysToDue($date)
    {
        if (!$date) return -1;
        $today = new DateTime(date('Y-m-d'));
        $due = new DateTime($date);
        return $due >= $today ? (int)$today->diff($due)->days : -1;
    }

    private function first(array $values)
    {
        foreach ($values as $value) if (trim((string)$value) !== '') return $value;
        return '';
    }

    private function normalizeEnvironment($environment)
    {
        return strtolower(trim((string)$environment)) === 'production' ? 'production' : 'sandbox';
    }
}
