<?php
declare(strict_types=1);

namespace App\Modules\Accounting;

use PDO;

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

    /**
     * @return list<array<string, mixed>>
     */
    public function findByOrderId(int $orderId): array
    {
        $statement = $this->pdo->prepare(
            'SELECT r.*, rc.name AS config_name
             FROM receipts r
             LEFT JOIN receipt_configs rc ON rc.id = r.config_id
             WHERE r.order_id = :order_id
             ORDER BY r.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 * FROM receipts WHERE id = :id LIMIT 1');
        $statement->execute(['id' => $id]);
        $row = $statement->fetch(PDO::FETCH_ASSOC);

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

    /**
     * @param list<int> $ids
     * @return list<array<string, mixed>>
     */
    public function findByIds(array $ids): array
    {
        if ($ids === []) {
            return [];
        }

        $placeholders = implode(',', array_fill(0, count($ids), '?'));
        $statement = $this->pdo->prepare(
            "SELECT r.*, rc.name AS config_name,
                    o.internal_order_number, o.external_order_id
             FROM receipts r
             LEFT JOIN receipt_configs rc ON rc.id = r.config_id
             LEFT JOIN orders o ON o.id = r.order_id
             WHERE r.id IN ({$placeholders})
             ORDER BY r.issue_date DESC"
        );
        $statement->execute(array_values($ids));
        $rows = $statement->fetchAll(PDO::FETCH_ASSOC);

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

    /**
     * @param array<string, mixed> $data
     */
    public function create(array $data): int
    {
        $statement = $this->pdo->prepare(
            'INSERT INTO receipts (
                order_id, config_id, receipt_number, issue_date, sale_date,
                seller_data_json, buyer_data_json, items_json,
                total_net, total_gross, order_reference_value, created_by
            ) VALUES (
                :order_id, :config_id, :receipt_number, :issue_date, :sale_date,
                :seller_data_json, :buyer_data_json, :items_json,
                :total_net, :total_gross, :order_reference_value, :created_by
            )'
        );

        $statement->execute([
            'order_id' => (int) $data['order_id'],
            'config_id' => (int) $data['config_id'],
            'receipt_number' => (string) $data['receipt_number'],
            'issue_date' => (string) $data['issue_date'],
            'sale_date' => (string) $data['sale_date'],
            '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,
            'created_by' => $data['created_by'] ?? null,
        ]);

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

    public function getNextNumber(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 receipt_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 receipt_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 receipt_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 receipt_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();

        $number = 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
        );

        return $number;
    }

    /**
     * @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 = [];

        $search = trim((string) ($filters['search'] ?? ''));
        if ($search !== '') {
            $where[] = '(r.receipt_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 . '%';
        }

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

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

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

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

        $allowedSort = ['receipt_number', 'issue_date', 'total_gross'];
        $sort = in_array((string) ($filters['sort'] ?? ''), $allowedSort, true)
            ? (string) $filters['sort']
            : 'issue_date';
        $sortDir = strtoupper((string) ($filters['sort_dir'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';

        $countStmt = $this->pdo->prepare(
            "SELECT COUNT(*) FROM receipts r
             LEFT JOIN orders o ON o.id = r.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'] ?? 20)));
        $offset = ($page - 1) * $perPage;

        $stmt = $this->pdo->prepare(
            "SELECT r.*, rc.name AS config_name,
                    o.internal_order_number, o.external_order_id
             FROM receipts r
             LEFT JOIN receipt_configs rc ON rc.id = r.config_id
             LEFT JOIN orders o ON o.id = r.order_id
             {$whereClause}
             ORDER BY r.{$sort} {$sortDir}
             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,
        ];
    }

    /**
     * @param array<string, mixed> $filters
     * @return list<array<string, mixed>>
     */
    public function exportData(array $filters): array
    {
        $where = [];
        $params = [];

        $search = trim((string) ($filters['search'] ?? ''));
        if ($search !== '') {
            $where[] = '(r.receipt_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 . '%';
        }

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

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

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

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

        $stmt = $this->pdo->prepare(
            "SELECT r.*, rc.name AS config_name,
                    o.internal_order_number, o.external_order_id
             FROM receipts r
             LEFT JOIN receipt_configs rc ON rc.id = r.config_id
             LEFT JOIN orders o ON o.id = r.order_id
             {$whereClause}
             ORDER BY r.issue_date DESC"
        );
        $stmt->execute($params);
        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

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