<?php

class FinanceCenterAssignmentService
{
    private $pdo;
    private $settingsCache;
    private $tableCache = array();

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function isInstalled()
    {
        return $this->tableExists('finance_center_assignment_settings');
    }

    public function settings()
    {
        if ($this->settingsCache !== null) return $this->settingsCache;
        $defaults = array(
            'id' => 1,
            'assignment_priority' => 'supplier_category_global',
            'global_cost_center_id' => null,
            'global_cost_center_name' => null,
            'auto_apply_manual_costs' => 1,
            'auto_apply_dte' => 1,
            'auto_apply_recurring' => 1,
            'installed' => $this->isInstalled() ? 1 : 0,
        );
        if (!$this->isInstalled()) return $this->settingsCache = $defaults;

        $row = $this->pdo->query("SELECT s.*,fc.center_name AS global_cost_center_name FROM finance_center_assignment_settings s LEFT JOIN finance_centers fc ON fc.id=s.global_cost_center_id WHERE s.id=1 LIMIT 1")->fetch(PDO::FETCH_ASSOC);
        if (!$row) return $this->settingsCache = $defaults;
        $row['installed'] = 1;
        return $this->settingsCache = array_merge($defaults, $row);
    }

    public function workspace()
    {
        return array(
            'settings' => $this->settings(),
            'centers' => $this->centers(),
            'suppliers' => $this->suppliers(),
            'categories' => $this->categories(),
            'coverage' => $this->coverage(),
        );
    }

    public function saveSettings(array $data, $userId)
    {
        $this->assertInstalled();
        $priority = trim((string)($data['assignment_priority'] ?? 'supplier_category_global'));
        $allowed = array('supplier_category_global', 'category_supplier_global');
        if (!in_array($priority, $allowed, true)) throw new InvalidArgumentException('La prioridad de asignación no es válida.');
        $centerId = (int)($data['global_cost_center_id'] ?? 0);
        if ($centerId && !$this->validCostCenter($centerId)) throw new InvalidArgumentException('El centro global no es válido o no está activo.');

        $stmt = $this->pdo->prepare("INSERT INTO finance_center_assignment_settings (id,assignment_priority,global_cost_center_id,auto_apply_manual_costs,auto_apply_dte,auto_apply_recurring,updated_by,created_at,updated_at) VALUES (1,?,?,?,?,?,?,NOW(),NOW()) ON DUPLICATE KEY UPDATE assignment_priority=VALUES(assignment_priority),global_cost_center_id=VALUES(global_cost_center_id),auto_apply_manual_costs=VALUES(auto_apply_manual_costs),auto_apply_dte=VALUES(auto_apply_dte),auto_apply_recurring=VALUES(auto_apply_recurring),updated_by=VALUES(updated_by),updated_at=NOW()");
        $stmt->execute(array(
            $priority,
            $centerId ?: null,
            $this->boolValue($data['auto_apply_manual_costs'] ?? 0),
            $this->boolValue($data['auto_apply_dte'] ?? 0),
            $this->boolValue($data['auto_apply_recurring'] ?? 0),
            (int)$userId ?: null,
        ));
        $this->settingsCache = null;
        return $this->settings();
    }

    public function assignSupplier($supplierId, $centerId)
    {
        $supplierId = (int)$supplierId;
        $centerId = (int)$centerId;
        if ($supplierId <= 0) throw new InvalidArgumentException('Proveedor no válido.');
        if ($centerId && !$this->validCostCenter($centerId)) throw new InvalidArgumentException('Centro de costo no válido.');
        $check = $this->pdo->prepare("SELECT id FROM crm_customers WHERE id=? AND deleted_at IS NULL AND (is_supplier=1 OR counterparty_type IN ('supplier','both')) LIMIT 1");
        $check->execute(array($supplierId));
        if (!$check->fetchColumn()) throw new RuntimeException('El proveedor ya no está disponible.');
        $stmt = $this->pdo->prepare('UPDATE crm_customers SET supplier_default_cost_center_id=?,default_cost_center_id=?,updated_at=NOW() WHERE id=?');
        $stmt->execute(array($centerId ?: null, $centerId ?: null, $supplierId));
    }

    public function assignCategory($categoryId, $centerId)
    {
        $categoryId = (int)$categoryId;
        $centerId = (int)$centerId;
        if ($categoryId <= 0) throw new InvalidArgumentException('Categoría no válida.');
        if ($centerId && !$this->validCostCenter($centerId)) throw new InvalidArgumentException('Centro de costo no válido.');
        $stmt = $this->pdo->prepare('UPDATE cost_categories SET default_finance_center_id=?,updated_at=NOW() WHERE id=?');
        $stmt->execute(array($centerId ?: null, $categoryId));
        if (!$stmt->rowCount()) {
            $check = $this->pdo->prepare('SELECT id FROM cost_categories WHERE id=?');
            $check->execute(array($categoryId));
            if (!$check->fetchColumn()) throw new RuntimeException('La categoría ya no está disponible.');
        }
    }

    public function resolveCostCenter($supplierId, $categoryId, $explicitCenterId, $context)
    {
        $explicitCenterId = (int)$explicitCenterId;
        if ($explicitCenterId && $this->validCostCenter($explicitCenterId)) {
            return $this->resolution($explicitCenterId, 'manual');
        }

        $settings = $this->settings();
        $flag = array('manual' => 'auto_apply_manual_costs', 'dte' => 'auto_apply_dte', 'recurring' => 'auto_apply_recurring');
        $context = isset($flag[$context]) ? $context : 'manual';
        if (empty($settings[$flag[$context]])) return $this->resolution(null, 'none');

        $order = $settings['assignment_priority'] === 'category_supplier_global'
            ? array('category', 'supplier', 'global')
            : array('supplier', 'category', 'global');
        foreach ($order as $source) {
            $centerId = null;
            if ($source === 'supplier' && (int)$supplierId > 0) {
                $stmt = $this->pdo->prepare('SELECT COALESCE(supplier_default_cost_center_id,default_cost_center_id) FROM crm_customers WHERE id=? AND deleted_at IS NULL LIMIT 1');
                $stmt->execute(array((int)$supplierId));
                $centerId = (int)$stmt->fetchColumn();
            } elseif ($source === 'category' && (int)$categoryId > 0 && $this->tableExists('cost_categories')) {
                $stmt = $this->pdo->prepare('SELECT default_finance_center_id FROM cost_categories WHERE id=? AND is_active=1 LIMIT 1');
                $stmt->execute(array((int)$categoryId));
                $centerId = (int)$stmt->fetchColumn();
            } elseif ($source === 'global') {
                $centerId = (int)($settings['global_cost_center_id'] ?? 0);
            }
            if ($centerId && $this->validCostCenter($centerId)) return $this->resolution($centerId, $source);
        }
        return $this->resolution(null, 'none');
    }

    public function applyPending($period, $userId)
    {
        $this->assertInstalled();
        $period = trim((string)$period);
        if ($period !== '' && !preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $period)) throw new InvalidArgumentException('El período debe usar el formato AAAA-MM.');
        $result = array('documents' => 0, 'collections' => 0, 'cost_entries' => 0, 'recurring_rules' => 0);

        $this->pdo->beginTransaction();
        try {
            $settings = $this->settings();
            if (!empty($settings['auto_apply_dte']) && $this->tableExists('facto_inbox_documents')) {
                $placeholderId = $this->placeholderCostCenterId();
                $sql = "SELECT id,inbox_document_id,supplier_customer_id FROM facto_inbox_documents WHERE environment='production' AND (finance_center_id IS NULL" . ($placeholderId ? ' OR finance_center_id=' . (int)$placeholderId : '') . ')';
                $params = array();
                if ($period !== '') { $sql .= " AND DATE_FORMAT(COALESCE(document_date,receive_date),'%Y-%m')=?"; $params[] = $period; }
                $sql .= ' ORDER BY id LIMIT 5000';
                $stmt = $this->pdo->prepare($sql); $stmt->execute($params);
                $updateDoc = $this->pdo->prepare('UPDATE facto_inbox_documents SET finance_center_id=?,updated_at=NOW() WHERE id=? AND (finance_center_id IS NULL' . ($placeholderId ? ' OR finance_center_id=' . (int)$placeholderId : '') . ')');
                $updateCollection = $this->pdo->prepare("UPDATE billing_collections SET finance_center_id=?,updated_at=NOW() WHERE environment='production' AND direction='payable' AND BINARY inbox_document_id=BINARY ? AND (finance_center_id IS NULL" . ($placeholderId ? ' OR finance_center_id=' . (int)$placeholderId : '') . ')');
                foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
                    $resolved = $this->resolveCostCenter((int)$row['supplier_customer_id'], 0, 0, 'dte');
                    if (empty($resolved['center_id'])) continue;
                    $updateDoc->execute(array($resolved['center_id'], (int)$row['id']));
                    $result['documents'] += $updateDoc->rowCount();
                    $updateCollection->execute(array($resolved['center_id'], $row['inbox_document_id']));
                    $result['collections'] += $updateCollection->rowCount();
                }
            }

            if ($this->tableExists('cost_recurring_rules') && !empty($settings['auto_apply_recurring'])) {
                $placeholderId = $this->placeholderCostCenterId();
                $stmt = $this->pdo->query('SELECT id,supplier_id,category_id FROM cost_recurring_rules WHERE (finance_center_id IS NULL' . ($placeholderId ? ' OR finance_center_id=' . (int)$placeholderId : '') . ') AND is_active=1 ORDER BY id LIMIT 5000');
                $update = $this->pdo->prepare('UPDATE cost_recurring_rules SET finance_center_id=?,updated_by=?,updated_at=NOW() WHERE id=? AND (finance_center_id IS NULL' . ($placeholderId ? ' OR finance_center_id=' . (int)$placeholderId : '') . ')');
                foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
                    $resolved = $this->resolveCostCenter($row['supplier_id'], $row['category_id'], 0, 'recurring');
                    if (empty($resolved['center_id'])) continue;
                    $update->execute(array($resolved['center_id'], (int)$userId ?: null, (int)$row['id']));
                    $result['recurring_rules'] += $update->rowCount();
                }
            }

            if ($this->tableExists('cost_entries')) {
                $placeholderId = $this->placeholderCostCenterId();
                $sql = "SELECT e.id,e.period,e.source_type,e.supplier_id,e.category_id,e.management_cost_amount FROM cost_entries e WHERE e.status<>'void' AND (e.finance_center_id IS NULL" . ($placeholderId ? ' OR e.finance_center_id=' . (int)$placeholderId : '') . ") AND NOT EXISTS (SELECT 1 FROM cost_allocations a WHERE a.cost_entry_id=e.id" . ($placeholderId ? ' AND a.finance_center_id<>' . (int)$placeholderId : '') . ")";
                $params = array();
                if ($period !== '') { $sql .= ' AND e.period=?'; $params[] = $period; }
                $sql .= ' ORDER BY e.id LIMIT 5000';
                $stmt = $this->pdo->prepare($sql); $stmt->execute($params);
                $update = $this->pdo->prepare('UPDATE cost_entries SET finance_center_id=?,updated_by=?,updated_at=NOW() WHERE id=? AND (finance_center_id IS NULL' . ($placeholderId ? ' OR finance_center_id=' . (int)$placeholderId : '') . ')');
                $deletePlaceholder = $placeholderId ? $this->pdo->prepare('DELETE FROM cost_allocations WHERE cost_entry_id=? AND finance_center_id=?') : null;
                $insert = $this->pdo->prepare('INSERT IGNORE INTO cost_allocations (cost_entry_id,finance_center_id,allocation_percent,allocated_management_amount) VALUES (?,?,100,?)');
                foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
                    $context = $row['source_type'] === 'dte' ? 'dte' : ($row['source_type'] === 'recurring' ? 'recurring' : 'manual');
                    $resolved = $this->resolveCostCenter($row['supplier_id'], $row['category_id'], 0, $context);
                    if (empty($resolved['center_id'])) continue;
                    $update->execute(array($resolved['center_id'], (int)$userId ?: null, (int)$row['id']));
                    if ($update->rowCount()) {
                        if ($deletePlaceholder) $deletePlaceholder->execute(array((int)$row['id'], $placeholderId));
                        $insert->execute(array((int)$row['id'], $resolved['center_id'], $row['management_cost_amount']));
                        $result['cost_entries']++;
                    }
                }
            }
            $this->pdo->commit();
            return $result;
        } catch (Throwable $e) {
            if ($this->pdo->inTransaction()) $this->pdo->rollBack();
            throw $e;
        }
    }

    private function resolution($centerId, $source)
    {
        $labels = array('manual' => 'Asignación manual', 'supplier' => 'Predeterminado del proveedor', 'category' => 'Predeterminado de la categoría', 'global' => 'Respaldo global', 'none' => 'Sin asignación automática');
        $name = null;
        if ($centerId) {
            $stmt = $this->pdo->prepare('SELECT center_name FROM finance_centers WHERE id=? LIMIT 1');
            $stmt->execute(array((int)$centerId));
            $name = $stmt->fetchColumn() ?: null;
        }
        return array('center_id' => $centerId ? (int)$centerId : null, 'center_name' => $name, 'source' => $source, 'source_label' => $labels[$source] ?? $source);
    }

    private function centers()
    {
        return $this->pdo->query("SELECT id,center_code,center_name,center_type,is_default FROM finance_centers WHERE is_active=1 AND center_type IN ('cost','both') AND center_code<>'NO_COST_CENTER' ORDER BY is_default DESC,center_name")->fetchAll(PDO::FETCH_ASSOC);
    }

    private function suppliers()
    {
        return $this->pdo->query("SELECT id,COALESCE(NULLIF(commercial_name,''),NULLIF(customer_name,''),legal_name) AS display_name,customer_tax_id,COALESCE(supplier_default_cost_center_id,default_cost_center_id) AS finance_center_id FROM crm_customers WHERE deleted_at IS NULL AND (is_supplier=1 OR counterparty_type IN ('supplier','both')) AND COALESCE(supplier_status,'active')='active' ORDER BY display_name LIMIT 1000")->fetchAll(PDO::FETCH_ASSOC);
    }

    private function categories()
    {
        if (!$this->tableExists('cost_categories')) return array();
        return $this->pdo->query('SELECT id,category_code,category_name,category_group,default_finance_center_id AS finance_center_id,is_active FROM cost_categories ORDER BY is_active DESC,category_group,category_name')->fetchAll(PDO::FETCH_ASSOC);
    }

    private function coverage()
    {
        $coverage = array('suppliers_total' => 0, 'suppliers_unassigned' => 0, 'categories_total' => 0, 'categories_unassigned' => 0, 'cost_entries_unassigned' => 0, 'documents_unassigned' => 0);
        $coverage['suppliers_total'] = (int)$this->pdo->query("SELECT COUNT(*) FROM crm_customers WHERE deleted_at IS NULL AND (is_supplier=1 OR counterparty_type IN ('supplier','both')) AND COALESCE(supplier_status,'active')='active'")->fetchColumn();
        $coverage['suppliers_unassigned'] = (int)$this->pdo->query("SELECT COUNT(*) FROM crm_customers c LEFT JOIN finance_centers fc ON fc.id=COALESCE(c.supplier_default_cost_center_id,c.default_cost_center_id) WHERE c.deleted_at IS NULL AND (c.is_supplier=1 OR c.counterparty_type IN ('supplier','both')) AND COALESCE(c.supplier_status,'active')='active' AND (fc.id IS NULL OR fc.center_code='NO_COST_CENTER')")->fetchColumn();
        if ($this->tableExists('cost_categories')) {
            $coverage['categories_total'] = (int)$this->pdo->query('SELECT COUNT(*) FROM cost_categories WHERE is_active=1')->fetchColumn();
            $coverage['categories_unassigned'] = (int)$this->pdo->query("SELECT COUNT(*) FROM cost_categories c LEFT JOIN finance_centers fc ON fc.id=c.default_finance_center_id WHERE c.is_active=1 AND (fc.id IS NULL OR fc.center_code='NO_COST_CENTER')")->fetchColumn();
        }
        if ($this->tableExists('cost_entries')) $coverage['cost_entries_unassigned'] = (int)$this->pdo->query("SELECT COUNT(*) FROM cost_entries e LEFT JOIN finance_centers fc ON fc.id=e.finance_center_id WHERE e.status<>'void' AND (fc.id IS NULL OR fc.center_code='NO_COST_CENTER') AND NOT EXISTS (SELECT 1 FROM cost_allocations a JOIN finance_centers afc ON afc.id=a.finance_center_id WHERE a.cost_entry_id=e.id AND afc.center_code<>'NO_COST_CENTER')")->fetchColumn();
        if ($this->tableExists('facto_inbox_documents')) $coverage['documents_unassigned'] = (int)$this->pdo->query("SELECT COUNT(*) FROM facto_inbox_documents d LEFT JOIN finance_centers fc ON fc.id=d.finance_center_id WHERE d.environment='production' AND (fc.id IS NULL OR fc.center_code='NO_COST_CENTER')")->fetchColumn();
        return $coverage;
    }

    private function validCostCenter($id)
    {
        $stmt = $this->pdo->prepare("SELECT COUNT(*) FROM finance_centers WHERE id=? AND is_active=1 AND center_type IN ('cost','both') AND center_code<>'NO_COST_CENTER'");
        $stmt->execute(array((int)$id));
        return (int)$stmt->fetchColumn() > 0;
    }

    private function tableExists($table)
    {
        if (array_key_exists($table, $this->tableCache)) return $this->tableCache[$table];
        $stmt = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name=?');
        $stmt->execute(array($table));
        return $this->tableCache[$table] = ((int)$stmt->fetchColumn() > 0);
    }

    private function boolValue($value)
    {
        return in_array((string)$value, array('1', 'true', 'on', 'yes'), true) ? 1 : 0;
    }

    private function placeholderCostCenterId()
    {
        $stmt = $this->pdo->query("SELECT id FROM finance_centers WHERE center_code='NO_COST_CENTER' LIMIT 1");
        return (int)$stmt->fetchColumn();
    }

    private function assertInstalled()
    {
        if (!$this->isInstalled()) throw new RuntimeException('Instala la migración v8.9.1 para habilitar la asignación centralizada.');
    }
}
