<?php

declare(strict_types=1);

final class DirectoryController
{
    private static function roles(): array
    {
        return [
            'adoptant' => t('role.adoptant'),
            'benevole' => t('role.benevole'),
            'cabinet' => t('role.cabinet'),
            'crematorium' => t('role.crematorium'),
            'deposant' => t('role.deposant'),
            'dirigeant' => t('role.dirigeant'),
            'fa' => t('role.fa'),
            'fourriere' => t('role.fourriere'),
            'veterinaire' => t('role.veterinaire'),
        ];
    }

    public static function index(): void
    {
        $db = DB::pdo();
        $q = trim((string) ($_GET['q'] ?? ''));
        $role = trim((string) ($_GET['role'] ?? ''));
        $where = ['dc.deleted_at IS NULL'];
        $params = [];
        if ($q !== '') {
            $where[] = '(dc.name LIKE :q OR dc.phone LIKE :q OR dc.email LIKE :q OR dc.city LIKE :q)';
            $params[':q'] = '%' . $q . '%';
        }
        $roles = self::roles();
        if (isset($roles[$role])) {
            $where[] = 'EXISTS(SELECT 1 FROM directory_contact_roles f WHERE f.contact_id=dc.id AND f.role=:role)';
            $params[':role'] = $role;
        }
        $stmt = $db->prepare(
            "
        SELECT dc.*, org.name AS organization_name, group_concat(dcr.role, ',') AS roles,
          (SELECT COUNT(*) FROM (
            SELECT animal_id FROM adoptions ad WHERE ad.adopter_contact_id=dc.id OR lower(trim(ad.adopter_name))=lower(trim(dc.name))
            UNION SELECT animal_id FROM animal_placements ap WHERE ap.contact_id=dc.id AND ap.event_type='adoption'
            UNION SELECT animal_id FROM animal_movements am WHERE am.kind='exit' AND lower(trim(am.contact_name))=lower(trim(dc.name)) AND lower(COALESCE(am.lieu,'')) LIKE 'adopt%'
          )) AS adoption_count,
          (SELECT COALESCE(SUM(ae.total_cents),0) FROM animal_expenses ae WHERE ae.clinic_contact_id=dc.id) AS expense_total_cents
        FROM directory_contacts dc
        LEFT JOIN directory_contacts org ON org.id=dc.organization_id
        LEFT JOIN directory_contact_roles dcr ON dcr.contact_id=dc.id
        WHERE " .
                implode(' AND ', $where) .
                '
        GROUP BY dc.id ORDER BY dc.name COLLATE NOCASE
        ',
        );
        $stmt->execute($params);
        render('directory_list.php', [
            'title' => t('directory.title'),
            'contacts' => $stmt->fetchAll(),
            'roles' => $roles,
            'q' => $q,
            'role' => $role,
        ]);
    }

    public static function show(): void
    {
        $db = DB::pdo();
        $id = (int) ($_GET['id'] ?? 0);
        $stmt = $db->prepare(
            "SELECT dc.*,org.name organization_name,group_concat(r.role,',') roles FROM directory_contacts dc LEFT JOIN directory_contacts org ON org.id=dc.organization_id LEFT JOIN directory_contact_roles r ON r.contact_id=dc.id WHERE dc.id=:id AND dc.deleted_at IS NULL GROUP BY dc.id",
        );
        $stmt->execute([':id' => $id]);
        $contact = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$contact) {
            http_response_code(404);
            echo h(t('directory.not_found'));
            return;
        }
        $adoptions = $db->prepare("SELECT a.id,a.name,a.internal_code,a.species,a.status,x.event_date FROM animals a JOIN (SELECT animal_id,MAX(event_date) event_date FROM (
          SELECT animal_id,adoption_date event_date FROM adoptions WHERE adopter_contact_id=:id1 OR lower(trim(adopter_name))=lower(trim(:name1))
          UNION ALL SELECT animal_id,event_date FROM animal_placements WHERE contact_id=:id2 AND event_type='adoption'
          UNION ALL SELECT animal_id,created_at event_date FROM animal_movements WHERE kind='exit' AND lower(trim(contact_name))=lower(trim(:name2)) AND lower(COALESCE(lieu,'')) LIKE 'adopt%'
        ) GROUP BY animal_id) x ON x.animal_id=a.id ORDER BY date(x.event_date) DESC,a.name COLLATE NOCASE");
        $adoptions->execute([':id1' => $id, ':name1' => $contact['name'], ':id2' => $id, ':name2' => $contact['name']]);
        $fosters = $db->prepare(
            "SELECT a.id,a.name,a.internal_code,a.species,a.status,MAX(ap.event_date) event_date FROM animal_placements ap JOIN animals a ON a.id=ap.animal_id WHERE ap.contact_id=:id AND ap.event_type='foster' GROUP BY a.id ORDER BY date(event_date) DESC,a.name COLLATE NOCASE",
        );
        $fosters->execute([':id' => $id]);
        $deposited = $db->prepare(
            'SELECT id,name,internal_code,species,status,intake_date event_date FROM animals WHERE depositor_contact_id=:id ORDER BY date(intake_date) DESC,name COLLATE NOCASE',
        );
        $deposited->execute([':id' => $id]);
        $periodRequested = array_key_exists('year', $_GET);
        $periodRaw = (string) ($_GET['year'] ?? 'all');
        $periodYear =
            ctype_digit($periodRaw) && (int) $periodRaw >= 2000 && (int) $periodRaw <= 2100 ? (int) $periodRaw : null;
        $periodMunicipality = trim((string) ($_GET['municipality'] ?? ''));
        $expenseWhere = ['ae.clinic_contact_id=:id', 'a.deleted_at IS NULL'];
        $expenseParams = [':id' => $id];
        if ($periodYear !== null) {
            $expenseWhere[] = 'date(ae.occurred_on) BETWEEN :expense_start AND :expense_end';
            $expenseParams[':expense_start'] = sprintf('%04d-01-01', $periodYear);
            $expenseParams[':expense_end'] = sprintf('%04d-12-31', $periodYear);
        }
        $expenses = $db->prepare(
            'SELECT ae.*,a.name animal_name,a.internal_code,a.rescue_location_name,a.rescue_address FROM animal_expenses ae JOIN animals a ON a.id=ae.animal_id WHERE ' .
                implode(' AND ', $expenseWhere) .
                ' ORDER BY date(ae.occurred_on) DESC,ae.id DESC',
        );
        $expenses->execute($expenseParams);
        $expenseRows = $expenses->fetchAll(PDO::FETCH_ASSOC);
        if ($periodMunicipality !== '') {
            $expenseRows = array_values(
                array_filter(
                    $expenseRows,
                    static fn($row) => GrantService::municipality(
                        (string) ($row['rescue_location_name'] ?: $row['rescue_address']),
                    ) === $periodMunicipality,
                ),
            );
        }
        $expenseTotal = array_sum(array_map(static fn($row) => (int) $row['total_cents'], $expenseRows));
        $expenseCount = count($expenseRows);
        $expenseRows = array_slice($expenseRows, 0, 250);
        $invoiceWhere = ['vendor_contact_id=:vendor', 'deleted_at IS NULL'];
        $invoiceParams = [':vendor' => $id];
        if ($periodYear !== null) {
            $invoiceWhere[] = "strftime('%Y',invoice_date)=:invoice_year";
            $invoiceParams[':invoice_year'] = (string) $periodYear;
        }
        $invoiceStmt = $db->prepare(
            'SELECT * FROM accounting_invoices WHERE ' .
                implode(' AND ', $invoiceWhere) .
                ' ORDER BY date(invoice_date) DESC,id DESC',
        );
        $invoiceStmt->execute($invoiceParams);
        $contactInvoices = $invoiceStmt->fetchAll(PDO::FETCH_ASSOC);
        $contactInvoiceTotal = array_sum(array_column($contactInvoices, 'total_ttc_cents'));
        $contactInvoiceDue = array_sum(array_column($contactInvoices, 'amount_due_cents'));
        $linkedPeople = [];
        if ($contact['kind'] === 'organization') {
            $s = $db->prepare(
                "SELECT dc.id,dc.name,dc.phone,dc.email,group_concat(r.role,',') roles FROM directory_contacts dc LEFT JOIN directory_contact_roles r ON r.contact_id=dc.id WHERE dc.organization_id=:id AND dc.deleted_at IS NULL GROUP BY dc.id ORDER BY dc.name COLLATE NOCASE",
            );
            $s->execute([':id' => $id]);
            $linkedPeople = $s->fetchAll(PDO::FETCH_ASSOC);
        }
        render('directory_show_accounting.php', [
            'title' => $contact['name'],
            'contact' => $contact,
            'roles' => self::roles(),
            'adoptions' => $adoptions->fetchAll(PDO::FETCH_ASSOC),
            'fosters' => $fosters->fetchAll(PDO::FETCH_ASSOC),
            'deposited' => $deposited->fetchAll(PDO::FETCH_ASSOC),
            'expenses' => $expenseRows,
            'expenseTotal' => $expenseTotal,
            'expenseCount' => $expenseCount,
            'expensePeriodRequested' => $periodRequested,
            'expensePeriodYear' => $periodYear,
            'expensePeriodMunicipality' => $periodMunicipality,
            'contactInvoices' => $contactInvoices,
            'contactInvoiceTotal' => $contactInvoiceTotal,
            'contactInvoiceDue' => $contactInvoiceDue,
            'linkedPeople' => $linkedPeople,
        ]);
    }

    public static function form(): void
    {
        $db = DB::pdo();
        $id = (int) ($_GET['id'] ?? 0);
        $contact = [
            'id' => 0,
            'kind' => 'person',
            'name' => '',
            'phone' => '',
            'email' => '',
            'address' => '',
            'postal_code' => '',
            'city' => '',
            'country' => 'France',
            'organization_id' => '',
            'notes' => '',
        ];
        $selected = [];
        if ($id) {
            $stmt = $db->prepare('SELECT * FROM directory_contacts WHERE id=:id AND deleted_at IS NULL');
            $stmt->execute([':id' => $id]);
            $contact = $stmt->fetch() ?: $contact;
            $stmt = $db->prepare('SELECT role FROM directory_contact_roles WHERE contact_id=:id');
            $stmt->execute([':id' => $id]);
            $selected = $stmt->fetchAll(PDO::FETCH_COLUMN);
        }
        $organizations = $db
            ->query(
                "SELECT id,name FROM directory_contacts WHERE kind='organization' AND deleted_at IS NULL ORDER BY name COLLATE NOCASE",
            )
            ->fetchAll();
        render('directory_form.php', [
            'title' => $id ? t('directory.edit') : t('directory.add'),
            'contact' => $contact,
            'selectedRoles' => $selected,
            'roles' => self::roles(),
            'organizations' => $organizations,
        ]);
    }

    public static function save(): void
    {
        if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
            http_response_code(405);
            return;
        }
        $db = DB::pdo();
        $id = (int) ($_POST['id'] ?? 0);
        $name = trim((string) ($_POST['name'] ?? ''));
        if ($name === '') {
            http_response_code(400);
            echo h(t('directory.name_required'));
            return;
        }
        $kind = ($_POST['kind'] ?? 'person') === 'organization' ? 'organization' : 'person';
        $roles = array_values(
            array_intersect(array_keys(self::roles()), is_array($_POST['roles'] ?? null) ? $_POST['roles'] : []),
        );
        $organizationId = $kind === 'person' ? ((int) ($_POST['organization_id'] ?? 0) ?: null) : null;
        if ($organizationId !== null) {
            $check = $db->prepare(
                "SELECT 1 FROM directory_contacts WHERE id=:id AND kind='organization' AND deleted_at IS NULL",
            );
            $check->execute([':id' => $organizationId]);
            if (!$check->fetchColumn() || $organizationId === $id) {
                http_response_code(400);
                echo h(t('directory.invalid_organization'));
                return;
            }
        }
        $data = [
            ':kind' => $kind,
            ':name' => $name,
            ':phone' => trim((string) ($_POST['phone'] ?? '')) ?: null,
            ':email' => trim((string) ($_POST['email'] ?? '')) ?: null,
            ':address' => trim((string) ($_POST['address'] ?? '')) ?: null,
            ':postal' => trim((string) ($_POST['postal_code'] ?? '')) ?: null,
            ':city' => trim((string) ($_POST['city'] ?? '')) ?: null,
            ':country' => trim((string) ($_POST['country'] ?? 'France')) ?: 'France',
            ':org' => $organizationId,
            ':notes' => trim((string) ($_POST['notes'] ?? '')) ?: null,
        ];
        $db->beginTransaction();
        try {
            if ($id) {
                $data[':id'] = $id;
                $db->prepare(
                    "UPDATE directory_contacts SET kind=:kind,name=:name,phone=:phone,email=:email,address=:address,postal_code=:postal,city=:city,country=:country,organization_id=:org,notes=:notes,updated_at=datetime('now') WHERE id=:id",
                )->execute($data);
            } else {
                $db->prepare(
                    'INSERT INTO directory_contacts(kind,name,phone,email,address,postal_code,city,country,organization_id,notes) VALUES(:kind,:name,:phone,:email,:address,:postal,:city,:country,:org,:notes)',
                )->execute($data);
                $id = (int) $db->lastInsertId();
            }
            $db->prepare('DELETE FROM directory_contact_roles WHERE contact_id=:id')->execute([':id' => $id]);
            $ins = $db->prepare('INSERT INTO directory_contact_roles(contact_id,role) VALUES(:id,:role)');
            foreach ($roles as $r) {
                $ins->execute([':id' => $id, ':role' => $r]);
            }
            $db->commit();
            header('Location: /directory');
            exit();
        } catch (Throwable $e) {
            if ($db->inTransaction()) {
                $db->rollBack();
            }
            throw $e;
        }
    }
}
