<?php

declare(strict_types=1);
final class AccountingController
{
    private static function dir(): string
    {
        $d = dirname(__DIR__, 2) . '/data/accounting-documents';
        if (!is_dir($d)) {
            mkdir($d, 0700, true);
        }
        return $d;
    }
    private static function vendors(PDO $db): array
    {
        return $db
            ->query(
                "SELECT DISTINCT dc.id,dc.name FROM directory_contacts dc JOIN directory_contact_roles r ON r.contact_id=dc.id WHERE dc.deleted_at IS NULL AND r.role IN ('cabinet','crematorium','fourriere','structure_veterinaire') ORDER BY dc.name COLLATE NOCASE",
            )
            ->fetchAll(PDO::FETCH_ASSOC);
    }
    public static function index(): void
    {
        $db = DB::pdo();
        $year = (int) ($_GET['year'] ?? 0);
        $where = ['ai.deleted_at IS NULL'];
        $params = [];
        if ($year) {
            $where[] = "strftime('%Y',ai.invoice_date)=:year";
            $params[':year'] = (string) $year;
        }
        $s = $db->prepare(
            'SELECT ai.*,dc.name vendor_name FROM accounting_invoices ai LEFT JOIN directory_contacts dc ON dc.id=ai.vendor_contact_id WHERE ' .
                implode(' AND ', $where) .
                ' ORDER BY date(ai.invoice_date) DESC,ai.id DESC',
        );
        $s->execute($params);
        $rows = $s->fetchAll(PDO::FETCH_ASSOC);
        $years = $db
            ->query(
                "SELECT DISTINCT strftime('%Y',invoice_date) y FROM accounting_invoices WHERE deleted_at IS NULL ORDER BY y DESC",
            )
            ->fetchAll(PDO::FETCH_COLUMN);
        render('accounting.php', [
            'title' => 'Comptabilité',
            'invoices' => $rows,
            'vendors' => self::vendors($db),
            'years' => $years,
            'year' => $year,
        ]);
    }
    public static function edit(): void
    {
        $id = (int) ($_GET['id'] ?? 0);
        $db = DB::pdo();
        $s = $db->prepare('SELECT * FROM accounting_invoices WHERE id=? AND deleted_at IS NULL');
        $s->execute([$id]);
        $invoice = $s->fetch(PDO::FETCH_ASSOC);
        if (!$invoice) {
            http_response_code(404);
            return;
        }
        render('accounting_edit.php', [
            'title' => 'Modifier la facture ' . $invoice['reference'],
            'invoice' => $invoice,
            'vendors' => self::vendors($db),
        ]);
    }
    public static function save(): void
    {
        $values = self::values();
        $file = self::upload();
        $db = DB::pdo();
        $s = $db->prepare(
            'INSERT INTO accounting_invoices(vendor_contact_id,reference,invoice_date,due_date,document_type,status,payment_method,paid_on,total_ht_cents,total_ttc_cents,amount_due_cents,notes,source,original_filename,stored_filename,mime_type,file_size,created_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
        );
        $s->execute([
            ...$values,
            'Saisie manuelle',
            $file['original'] ?? null,
            $file['stored'] ?? null,
            $file['mime'] ?? null,
            $file['size'] ?? null,
            Auth::id(),
        ]);
        AuditService::log(
            'accounting_invoice_created',
            '/accounting/save',
            'Facture ajoutée',
            'accounting_invoice',
            (int) $db->lastInsertId(),
            ['reference' => $values[1]],
        );
        header('Location: /accounting?year=0');
    }
    public static function update(): void
    {
        $id = (int) ($_POST['id'] ?? 0);
        $db = DB::pdo();
        $q = $db->prepare('SELECT * FROM accounting_invoices WHERE id=? AND deleted_at IS NULL');
        $q->execute([$id]);
        $old = $q->fetch(PDO::FETCH_ASSOC);
        if (!$old) {
            http_response_code(404);
            return;
        }
        $values = self::values();
        $file = self::upload();
        $sql =
            "UPDATE accounting_invoices SET vendor_contact_id=?,reference=?,invoice_date=?,due_date=?,document_type=?,status=?,payment_method=?,paid_on=?,total_ht_cents=?,total_ttc_cents=?,amount_due_cents=?,notes=?,updated_at=datetime('now')";
        if ($file) {
            $sql .= ',original_filename=?,stored_filename=?,mime_type=?,file_size=?';
            array_push($values, $file['original'], $file['stored'], $file['mime'], $file['size']);
        }
        $sql .= ' WHERE id=? AND deleted_at IS NULL';
        $values[] = $id;
        $db->prepare($sql)->execute($values);
        if ($file && !empty($old['stored_filename'])) {
            @unlink(self::dir() . '/' . basename((string) $old['stored_filename']));
        }
        AuditService::log(
            'accounting_invoice_updated',
            '/accounting/update',
            'Facture modifiée',
            'accounting_invoice',
            $id,
        );
        header('Location: /accounting?year=0');
    }
    public static function delete(): void
    {
        $id = (int) ($_POST['id'] ?? 0);
        $db = DB::pdo();
        $s = $db->prepare(
            "UPDATE accounting_invoices SET deleted_at=datetime('now'),deleted_by=?,updated_at=datetime('now') WHERE id=? AND deleted_at IS NULL",
        );
        $s->execute([Auth::id(), $id]);
        if ($s->rowCount()) {
            AuditService::log(
                'accounting_invoice_deleted',
                '/accounting/delete',
                'Facture mise à la corbeille',
                'accounting_invoice',
                $id,
            );
        }
        header('Location: /accounting?year=0');
    }
    public static function document(): void
    {
        $id = (int) ($_GET['id'] ?? 0);
        $s = DB::pdo()->prepare('SELECT * FROM accounting_invoices WHERE id=? AND deleted_at IS NULL');
        $s->execute([$id]);
        $r = $s->fetch(PDO::FETCH_ASSOC);
        if (!$r || !$r['stored_filename']) {
            http_response_code(404);
            return;
        }
        $p = self::dir() . '/' . basename((string) $r['stored_filename']);
        if (!is_file($p)) {
            http_response_code(404);
            return;
        }
        header('Content-Type: ' . $r['mime_type']);
        header(
            'Content-Disposition: inline; filename="' . str_replace('"', '', (string) $r['original_filename']) . '"',
        );
        header('Content-Length: ' . filesize($p));
        readfile($p);
    }
    private static function values(): array
    {
        $ref = trim((string) ($_POST['reference'] ?? ''));
        $date = (string) ($_POST['invoice_date'] ?? '');
        $ttc = self::money($_POST['total_ttc'] ?? '');
        if ($ref === '' || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $date) || $ttc === null) {
            http_response_code(400);
            exit(t('accounting.required_fields'));
        }
        $status = (string) ($_POST['status'] ?? 'to_pay');
        $due = self::money($_POST['amount_due'] ?? '');
        if ($due === null) {
            $due = $status === 'paid' ? 0 : $ttc;
        }
        return [
            (int) ($_POST['vendor_contact_id'] ?? 0) ?: null,
            $ref,
            $date,
            $_POST['due_date'] ?? '' ?: null,
            (string) ($_POST['document_type'] ?? 'invoice'),
            $status,
            $_POST['payment_method'] ?? '' ?: null,
            $_POST['paid_on'] ?? '' ?: null,
            self::money($_POST['total_ht'] ?? ''),
            $ttc,
            $due,
            trim((string) ($_POST['notes'] ?? '')) ?: null,
        ];
    }
    private static function money(mixed $v): ?int
    {
        $v = trim(str_replace([' ', '€', "\xc2\xa0"], ['', '', ''], (string) $v));
        if ($v === '') {
            return null;
        }
        $v = str_replace(',', '.', $v);
        return is_numeric($v) ? (int) round((float) $v * 100) : null;
    }
    private static function upload(): array
    {
        if (empty($_FILES['document']['tmp_name'])) {
            return [];
        }
        $f = $_FILES['document'];
        if (($f['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK || ($f['size'] ?? 0) > 20 * 1024 * 1024) {
            throw new RuntimeException(t('accounting.document_invalid'));
        }
        $mime = (new finfo(FILEINFO_MIME_TYPE))->file($f['tmp_name']);
        $ext =
            ['application/pdf' => 'pdf', 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp'][$mime] ??
            null;
        if (!$ext) {
            throw new RuntimeException(t('accounting.document_format'));
        }
        $stored = bin2hex(random_bytes(20)) . '.' . $ext;
        if (!move_uploaded_file($f['tmp_name'], self::dir() . '/' . $stored)) {
            throw new RuntimeException(t('accounting.document_save_failed'));
        }
        return [
            'original' => basename((string) $f['name']),
            'stored' => $stored,
            'mime' => $mime,
            'size' => (int) $f['size'],
        ];
    }
    public static function dashboard(): void
    {
        $db = DB::pdo();
        $year = (int) ($_GET['year'] ?? 0);
        $where = ['ai.deleted_at IS NULL'];
        $params = [];
        if ($year) {
            $where[] = "strftime('%Y',ai.invoice_date)=:year";
            $params[':year'] = (string) $year;
        }
        $sql =
            'SELECT ai.*,dc.name vendor_name FROM accounting_invoices ai LEFT JOIN directory_contacts dc ON dc.id=ai.vendor_contact_id WHERE ' .
            implode(' AND ', $where) .
            ' ORDER BY date(ai.invoice_date) DESC,ai.id DESC';
        $s = $db->prepare($sql);
        $s->execute($params);
        $rows = $s->fetchAll(PDO::FETCH_ASSOC);
        [$rows, $filters] = self::filterRows($rows);
        $vendors = [];
        $annual = [];
        $statuses = [];
        $methods = [];
        $totals = ['billed' => 0, 'due' => 0, 'documents' => count($rows), 'overdue' => 0, 'to_pay' => 0];
        foreach ($rows as $r) {
            $ttc = (int) $r['total_ttc_cents'];
            $due = (int) ($r['amount_due_cents'] ?? 0);
            $totals['billed'] += $ttc;
            $totals['due'] += $due;
            if ($due > 0) {
                $totals['to_pay']++;
            }
            if ($due > 0 && !empty($r['due_date']) && $r['due_date'] < date('Y-m-d')) {
                $totals['overdue']++;
            }
            $vendorId = (int) ($r['vendor_contact_id'] ?? 0);
            $vendorLabel = $r['vendor_name'] ?: 'Structure non renseignée';
            $vendors[$vendorId] ??= [
                'id' => $vendorId,
                'label' => $vendorLabel,
                'billed' => 0,
                'due' => 0,
                'documents' => 0,
                'paid_documents' => 0,
                'animals' => 0,
                'animal_cost' => 0,
            ];
            $vendors[$vendorId]['billed'] += $ttc;
            $vendors[$vendorId]['due'] += $due;
            $vendors[$vendorId]['documents']++;
            if ($r['status'] === 'paid') {
                $vendors[$vendorId]['paid_documents']++;
            }
            $y = substr((string) $r['invoice_date'], 0, 4);
            $annual[$y] ??= ['year' => $y, 'billed' => 0, 'due' => 0, 'documents' => 0];
            $annual[$y]['billed'] += $ttc;
            $annual[$y]['due'] += $due;
            $annual[$y]['documents']++;
            $statuses[$r['status']] = ($statuses[$r['status']] ?? 0) + 1;
            $method = $r['payment_method'] ?: 'unknown';
            $methods[$method] = ($methods[$method] ?? 0) + 1;
        }
        $expenseWhere = $year ? " AND strftime('%Y',ae.occurred_on)=:expense_year" : '';
        $expense = $db->prepare(
            "SELECT ae.clinic_contact_id,COUNT(DISTINCT ae.animal_id) animals,SUM(ae.total_cents) total FROM animal_expenses ae WHERE ae.clinic_contact_id IS NOT NULL $expenseWhere GROUP BY ae.clinic_contact_id",
        );
        $expense->execute($year ? [':expense_year' => (string) $year] : []);
        foreach ($expense->fetchAll(PDO::FETCH_ASSOC) as $e) {
            if (isset($vendors[(int) $e['clinic_contact_id']])) {
                $vendors[(int) $e['clinic_contact_id']]['animals'] = (int) $e['animals'];
                $vendors[(int) $e['clinic_contact_id']]['animal_cost'] = (int) $e['total'];
            }
        }
        usort($vendors, fn($a, $b) => $b['billed'] <=> $a['billed']);
        ksort($annual);
        $years = $db
            ->query(
                "SELECT DISTINCT strftime('%Y',invoice_date) y FROM accounting_invoices WHERE deleted_at IS NULL ORDER BY y DESC",
            )
            ->fetchAll(PDO::FETCH_COLUMN);
        render('accounting.php', [
            'title' => 'Comptabilité',
            'invoices' => $rows,
            'vendors' => self::vendors($db),
            'years' => $years,
            'year' => $year,
            'accountingFilters' => $filters,
            'accountingTotals' => $totals,
            'vendorStats' => $vendors,
            'annualStats' => array_values($annual),
            'statusStats' => $statuses,
            'methodStats' => $methods,
        ]);
    }
    public static function exportCsv(): void
    {
        self::export(false);
    }
    private static function export(bool $unused): void
    {
        $db = DB::pdo();
        $year = (int) ($_GET['year'] ?? 0);
        $where = ['ai.deleted_at IS NULL'];
        $params = [];
        if ($year) {
            $where[] = "strftime('%Y',ai.invoice_date)=:year";
            $params[':year'] = (string) $year;
        }
        $s = $db->prepare(
            'SELECT ai.*,dc.name vendor_name FROM accounting_invoices ai LEFT JOIN directory_contacts dc ON dc.id=ai.vendor_contact_id WHERE ' .
                implode(' AND ', $where) .
                ' ORDER BY date(ai.invoice_date),ai.id',
        );
        $s->execute($params);
        header('Content-Type: text/csv; charset=UTF-8');
        header(
            'Content-Disposition: attachment; filename="globinours-comptabilite-' .
                ($year ?: 'toutes-annees') .
                '.csv"',
        );
        echo "\xEF\xBB\xBF";
        $out = fopen('php://output', 'wb');
        fputcsv(
            $out,
            [
                'Date',
                'Référence',
                'Type',
                'Structure',
                'HT €',
                'TTC €',
                'Statut',
                'Reste dû €',
                'Règlement',
                'Échéance',
                'Notes',
            ],
            ';',
        );
        $safe = static function ($v) {
            $v = (string) $v;
            return preg_match('/^[=+\-@]/', $v) ? "'" . $v : $v;
        };
        foreach ($s->fetchAll(PDO::FETCH_ASSOC) as $r) {
            fputcsv(
                $out,
                [
                    $r['invoice_date'],
                    $safe($r['reference']),
                    $r['document_type'],
                    $safe($r['vendor_name'] ?? ''),
                    $r['total_ht_cents'] === null ? '' : number_format($r['total_ht_cents'] / 100, 2, '.', ''),
                    number_format($r['total_ttc_cents'] / 100, 2, '.', ''),
                    $r['status'],
                    number_format(($r['amount_due_cents'] ?? 0) / 100, 2, '.', ''),
                    $r['payment_method'],
                    $r['due_date'],
                    $safe($r['notes'] ?? ''),
                ],
                ';',
            );
        }
        fclose($out);
    }
    private static function filterRows(array $rows): array
    {
        $filters = [
            'q' => trim((string) ($_GET['q'] ?? '')),
            'vendor_id' => (int) ($_GET['vendor_id'] ?? 0),
            'status' => (string) ($_GET['status'] ?? ''),
            'type' => (string) ($_GET['type'] ?? ''),
            'payment_method' => (string) ($_GET['payment_method'] ?? ''),
        ];
        $rows = array_values(
            array_filter($rows, static function ($r) use ($filters) {
                if (
                    $filters['q'] !== '' &&
                    !str_contains(
                        mb_strtolower(
                            implode(' ', [$r['reference'] ?? '', $r['vendor_name'] ?? '', $r['notes'] ?? '']),
                        ),
                        mb_strtolower($filters['q']),
                    )
                ) {
                    return false;
                }
                if ($filters['vendor_id'] > 0 && (int) $r['vendor_contact_id'] !== $filters['vendor_id']) {
                    return false;
                }
                if ($filters['status'] !== '' && $r['status'] !== $filters['status']) {
                    return false;
                }
                if ($filters['type'] !== '' && $r['document_type'] !== $filters['type']) {
                    return false;
                }
                if ($filters['payment_method'] === 'unknown' && !empty($r['payment_method'])) {
                    return false;
                }
                if (
                    $filters['payment_method'] !== '' &&
                    $filters['payment_method'] !== 'unknown' &&
                    $r['payment_method'] !== $filters['payment_method']
                ) {
                    return false;
                }
                return true;
            }),
        );
        return [$rows, $filters];
    }
    public static function exportFilteredCsv(): void
    {
        $db = DB::pdo();
        $year = (int) ($_GET['year'] ?? 0);
        $s = $db->prepare(
            'SELECT ai.*,dc.name vendor_name FROM accounting_invoices ai LEFT JOIN directory_contacts dc ON dc.id=ai.vendor_contact_id WHERE ai.deleted_at IS NULL' .
                ($year ? " AND strftime('%Y',ai.invoice_date)=:year" : '') .
                ' ORDER BY date(ai.invoice_date),ai.id',
        );
        $s->execute($year ? [':year' => (string) $year] : []);
        [$rows] = self::filterRows($s->fetchAll(PDO::FETCH_ASSOC));
        header('Content-Type: text/csv; charset=UTF-8');
        header(
            'Content-Disposition: attachment; filename="globinours-comptabilite-' .
                ($year ?: 'toutes-annees') .
                '.csv"',
        );
        echo "\xEF\xBB\xBF";
        $out = fopen('php://output', 'wb');
        fputcsv(
            $out,
            [
                'Date',
                'Référence',
                'Type',
                'Structure',
                'HT €',
                'TTC €',
                'Statut',
                'Reste dû €',
                'Règlement',
                'Échéance',
                'Notes',
            ],
            ';',
        );
        $safe = static fn($v) => preg_match('/^[=+\-@]/', (string) $v) ? "'" . $v : (string) $v;
        foreach ($rows as $r) {
            fputcsv(
                $out,
                [
                    $r['invoice_date'],
                    $safe($r['reference']),
                    $r['document_type'],
                    $safe($r['vendor_name'] ?? ''),
                    $r['total_ht_cents'] === null ? '' : number_format($r['total_ht_cents'] / 100, 2, '.', ''),
                    number_format($r['total_ttc_cents'] / 100, 2, '.', ''),
                    $r['status'],
                    number_format(($r['amount_due_cents'] ?? 0) / 100, 2, '.', ''),
                    $r['payment_method'],
                    $r['due_date'],
                    $safe($r['notes'] ?? ''),
                ],
                ';',
            );
        }
        fclose($out);
    }
    public static function editByReference(): void
    {
        $ref = trim((string) ($_GET['reference'] ?? ''));
        $s = DB::pdo()->prepare(
            'SELECT id FROM accounting_invoices WHERE reference=? AND deleted_at IS NULL ORDER BY id DESC LIMIT 1',
        );
        $s->execute([$ref]);
        $_GET['id'] = (int) $s->fetchColumn();
        self::edit();
    }
}
