<?php
declare(strict_types=1);

namespace App\Modules\Accounting;

use PDO;

final class InvoiceRepository
{
    public function __construct(
        private readonly PDO $pdo
    ) {
    }

    /**
     * @return list<array<string, mixed>>
     */
    public function findByOrderId(int $orderId): array
    {
        $statement = $this->pdo->prepare(
            'SELECT i.*, ic.name AS config_name, ic.is_delegated AS config_is_delegated,
                    ig.name AS integration_name, fis.account_prefix
             FROM invoices i
             LEFT JOIN invoice_configs ic ON ic.id = i.config_id
             LEFT JOIN integrations ig ON ig.id = ic.integration_id AND ig.type = "fakturownia"
             LEFT JOIN fakturownia_integration_settings fis ON fis.integration_id = ig.id
             WHERE i.order_id = :order_id
             ORDER BY i.created_at DESC'
        );
        $statement->execute(['order_id' => $orderId]);
        $rows = $statement->fetchAll(PDO::FETCH_ASSOC);

        return is_array($rows) ? $rows : [];
    }

    /**
     * @return array<string, mixed>|null
     */
    public function findById(int $id): ?array
    {
        $statement = $this->pdo->prepare(
            'SELECT i.*, ic.name AS config_name, ic.is_delegated AS config_is_delegated,
                    ig.name AS integration_name, fis.account_prefix
             FROM invoices i
             LEFT JOIN invoice_configs ic ON ic.id = i.config_id
             LEFT JOIN integrations ig ON ig.id = ic.integration_id AND ig.type = "fakturownia"
             LEFT JOIN fakturownia_integration_settings fis ON fis.integration_id = ig.id
             WHERE i.id = :id LIMIT 1'
        );
        $statement->execute(['id' => $id]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);

        return is_array($row) ? $row : null;
    }

    /**
     * @param array<string, mixed> $data
     */
    public function insertLocal(array $data): int
    {
        return $this->insert($data, isDelegated: false);
    }

    /**
     * @param array<string, mixed> $data
     */
    public function insertDelegated(array $data): int
    {
        return $this->insert($data, isDelegated: true);
    }

    /**
     * @param array<string, mixed> $data
     */
    private function insert(array $data, bool $isDelegated): int
    {
        $statement = $this->pdo->prepare(
            'INSERT INTO invoices (
                order_id, config_id, invoice_number, issue_date, sale_date, payment_due_date,
                seller_data_json, buyer_data_json, items_json,
                total_net, total_gross, order_reference_value,
                external_invoice_id, external_pdf_url, kind, created_by
            ) VALUES (
                :order_id, :config_id, :invoice_number, :issue_date, :sale_date, :payment_due_date,
                :seller_data_json, :buyer_data_json, :items_json,
                :total_net, :total_gross, :order_reference_value,
                :external_invoice_id, :external_pdf_url, :kind, :created_by
            )'
        );

        $statement->execute([
            'order_id' => (int) $data['order_id'],
            'config_id' => (int) $data['config_id'],
            'invoice_number' => (string) $data['invoice_number'],
            'issue_date' => (string) $data['issue_date'],
            'sale_date' => (string) $data['sale_date'],
            'payment_due_date' => $data['payment_due_date'] ?? null,
            'seller_data_json' => (string) $data['seller_data_json'],
            'buyer_data_json' => $data['buyer_data_json'],
            'items_json' => (string) $data['items_json'],
            'total_net' => (string) $data['total_net'],
            'total_gross' => (string) $data['total_gross'],
            'order_reference_value' => $data['order_reference_value'] ?? null,
            'external_invoice_id' => $isDelegated ? ($data['external_invoice_id'] ?? null) : null,
            'external_pdf_url' => $isDelegated ? ($data['external_pdf_url'] ?? null) : null,
            'kind' => (string) ($data['kind'] ?? 'vat'),
            'created_by' => $data['created_by'] ?? null,
        ]);

        return (int) $this->pdo->lastInsertId();
    }

    public function nextLocalNumber(int $configId, string $numberFormat, string $numberingType): string
    {
        $year = (int) date('Y');
        $month = $numberingType === 'yearly' ? null : (int) date('n');

        if ($month === null) {
            $this->pdo->prepare(
                'INSERT INTO invoice_number_counters (config_id, year, month, last_number)
                 VALUES (:config_id, :year, NULL, 1)
                 ON DUPLICATE KEY UPDATE last_number = last_number + 1'
            )->execute(['config_id' => $configId, 'year' => $year]);

            $stmt = $this->pdo->prepare(
                'SELECT last_number FROM invoice_number_counters
                 WHERE config_id = :config_id AND year = :year AND month IS NULL'
            );
            $stmt->execute(['config_id' => $configId, 'year' => $year]);
        } else {
            $this->pdo->prepare(
                'INSERT INTO invoice_number_counters (config_id, year, month, last_number)
                 VALUES (:config_id, :year, :month, 1)
                 ON DUPLICATE KEY UPDATE last_number = last_number + 1'
            )->execute(['config_id' => $configId, 'year' => $year, 'month' => $month]);

            $stmt = $this->pdo->prepare(
                'SELECT last_number FROM invoice_number_counters
                 WHERE config_id = :config_id AND year = :year AND month = :month'
            );
            $stmt->execute(['config_id' => $configId, 'year' => $year, 'month' => $month]);
        }

        $lastNumber = (int) $stmt->fetchColumn();

        return str_replace(
            ['%N', '%M', '%Y'],
            [
                str_pad((string) $lastNumber, 3, '0', STR_PAD_LEFT),
                str_pad((string) ($month ?? 1), 2, '0', STR_PAD_LEFT),
                (string) $year,
            ],
            $numberFormat
        );
    }

    /**
     * @param array<string, mixed> $filters
     * @return array{items: list<array<string, mixed>>, total: int, page: int, per_page: int}
     */
    public function paginate(array $filters): array
    {
        $where = [];
        $params = [];

        $configId = (int) ($filters['config_id'] ?? 0);
        if ($configId > 0) {
            $where[] = 'i.config_id = :config_id';
            $params['config_id'] = $configId;
        }

        $mode = (string) ($filters['mode'] ?? '');
        if ($mode === 'local') {
            $where[] = 'i.external_invoice_id IS NULL';
        } elseif ($mode === 'delegated') {
            $where[] = 'i.external_invoice_id IS NOT NULL';
        }

        $dateFrom = trim((string) ($filters['date_from'] ?? ''));
        if ($dateFrom !== '' && strtotime($dateFrom) !== false) {
            $where[] = 'i.issue_date >= :date_from';
            $params['date_from'] = $dateFrom;
        }

        $dateTo = trim((string) ($filters['date_to'] ?? ''));
        if ($dateTo !== '' && strtotime($dateTo) !== false) {
            $where[] = 'i.issue_date <= :date_to';
            $params['date_to'] = $dateTo . ' 23:59:59';
        }

        $search = trim((string) ($filters['search'] ?? ''));
        if ($search !== '') {
            $where[] = '(i.invoice_number LIKE :search OR o.internal_order_number LIKE :search2 OR o.external_order_id LIKE :search3)';
            $params['search'] = '%' . $search . '%';
            $params['search2'] = '%' . $search . '%';
            $params['search3'] = '%' . $search . '%';
        }

        $whereClause = $where !== [] ? 'WHERE ' . implode(' AND ', $where) : '';

        $countStmt = $this->pdo->prepare(
            "SELECT COUNT(*) FROM invoices i
             LEFT JOIN orders o ON o.id = i.order_id
             {$whereClause}"
        );
        $countStmt->execute($params);
        $total = (int) $countStmt->fetchColumn();

        $page = max(1, (int) ($filters['page'] ?? 1));
        $perPage = max(1, min(100, (int) ($filters['per_page'] ?? 50)));
        $offset = ($page - 1) * $perPage;

        $stmt = $this->pdo->prepare(
            "SELECT i.*, ic.name AS config_name, ic.is_delegated AS config_is_delegated,
                    ig.name AS integration_name, fis.account_prefix,
                    o.internal_order_number, o.external_order_id
             FROM invoices i
             LEFT JOIN invoice_configs ic ON ic.id = i.config_id
             LEFT JOIN integrations ig ON ig.id = ic.integration_id AND ig.type = 'fakturownia'
             LEFT JOIN fakturownia_integration_settings fis ON fis.integration_id = ig.id
             LEFT JOIN orders o ON o.id = i.order_id
             {$whereClause}
             ORDER BY i.issue_date DESC, i.id DESC
             LIMIT {$perPage} OFFSET {$offset}"
        );
        $stmt->execute($params);
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

        return [
            'items' => is_array($rows) ? $rows : [],
            'total' => $total,
            'page' => $page,
            'per_page' => $perPage,
        ];
    }
}
