<?php
declare(strict_types=1);

namespace App\Modules\Accounting;

use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\CompanySettingsRepository;
use App\Modules\Settings\ReceiptConfigRepository;
use Throwable;

final class ReceiptService
{
    public function __construct(
        private readonly ReceiptRepository $receipts,
        private readonly ReceiptConfigRepository $receiptConfigs,
        private readonly CompanySettingsRepository $companySettings,
        private readonly OrdersRepository $orders
    ) {
    }

    /**
     * @param array{
     *     order_id: int,
     *     config_id: int,
     *     issue_date_mode?: string,
     *     issue_date_override?: string,
     *     created_by?: int|null,
     * } $params
     * @return array{receipt_number: string, total_gross: string}
     * @throws ReceiptIssueException
     */
    public function issue(array $params): array
    {
        $orderId = (int) $params['order_id'];
        $configId = (int) $params['config_id'];

        $config = $this->receiptConfigs->findById($configId);
        if ($config === null || (int) ($config['is_active'] ?? 0) !== 1) {
            throw new ReceiptIssueException('Nieprawidlowa lub nieaktywna konfiguracja paragonu');
        }

        $details = $this->orders->findDetails($orderId);
        if ($details === null) {
            throw new ReceiptIssueException('Zamowienie nie istnieje');
        }

        $order = is_array($details['order'] ?? null) ? $details['order'] : [];
        $items = is_array($details['items'] ?? null) ? $details['items'] : [];
        $addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : [];
        $payments = is_array($details['payments'] ?? null) ? $details['payments'] : [];

        $issueDateOverride = trim((string) ($params['issue_date_override'] ?? ''));
        if ($issueDateOverride !== '' && strtotime($issueDateOverride) !== false) {
            $issueDate = date('Y-m-d H:i:s', strtotime($issueDateOverride));
        } else {
            $issueDateMode = (string) ($params['issue_date_mode'] ?? 'today');
            $issueDate = $this->resolveIssueDate($issueDateMode, $order, $payments);
        }

        $saleDate = $this->resolveSaleDate($config, $order, $payments, $issueDate);
        $orderReference = $this->resolveOrderReference($config, $order);
        $sellerSnapshot = $this->buildSellerSnapshot();
        $buyerSnapshot = $this->buildBuyerSnapshot($addresses);
        ['items' => $itemsSnapshot, 'total_gross' => $totalGross] = $this->buildItemsSnapshot($items, $order);

        $receiptNumber = $this->receipts->getNextNumber(
            $configId,
            (string) ($config['number_format'] ?? 'PAR/%N/%M/%Y'),
            (string) ($config['numbering_type'] ?? 'monthly')
        );

        $this->receipts->create([
            'order_id' => $orderId,
            'config_id' => $configId,
            'receipt_number' => $receiptNumber,
            'issue_date' => $issueDate,
            'sale_date' => $saleDate,
            'seller_data_json' => json_encode($sellerSnapshot, JSON_UNESCAPED_UNICODE),
            'buyer_data_json' => $buyerSnapshot !== null ? json_encode($buyerSnapshot, JSON_UNESCAPED_UNICODE) : null,
            'items_json' => json_encode($itemsSnapshot, JSON_UNESCAPED_UNICODE),
            'total_net' => number_format($totalGross, 2, '.', ''),
            'total_gross' => number_format($totalGross, 2, '.', ''),
            'order_reference_value' => $orderReference,
            'created_by' => $params['created_by'] ?? null,
        ]);

        return [
            'receipt_number' => $receiptNumber,
            'total_gross' => number_format($totalGross, 2, '.', ''),
        ];
    }

    /**
     * @param list<array<string, mixed>> $items
     */
    public function calculateTotalGross(array $items, float $deliveryPrice = 0.0): float
    {
        $total = 0.0;
        foreach ($items as $item) {
            $qty = (float) ($item['quantity'] ?? 0);
            $price = $item['original_price_with_tax'] !== null ? (float) $item['original_price_with_tax'] : 0.0;
            $total += $qty * $price;
        }
        if ($deliveryPrice > 0) {
            $total += $deliveryPrice;
        }
        return $total;
    }

    /**
     * @param array<string, mixed> $order
     * @param list<array<string, mixed>> $payments
     */
    private function resolveIssueDate(string $mode, array $order, array $payments): string
    {
        if ($mode === 'order_date') {
            $orderedAt = trim((string) ($order['ordered_at'] ?? ''));
            if ($orderedAt !== '') {
                $timestamp = strtotime($orderedAt);
                if ($timestamp !== false) {
                    return date('Y-m-d H:i:s', $timestamp);
                }
            }
        }

        if ($mode === 'payment_date') {
            $firstPayment = $payments[0] ?? [];
            $paymentDate = trim((string) ($firstPayment['payment_date'] ?? ''));
            if ($paymentDate !== '') {
                $timestamp = strtotime($paymentDate);
                if ($timestamp !== false) {
                    return date('Y-m-d H:i:s', $timestamp);
                }
            }
        }

        return date('Y-m-d H:i:s');
    }

    /**
     * @param array<string, mixed> $config
     * @param array<string, mixed> $order
     * @param list<array<string, mixed>> $payments
     */
    private function resolveSaleDate(array $config, array $order, array $payments, string $issueDate): string
    {
        $source = (string) ($config['sale_date_source'] ?? 'issue_date');

        if ($source === 'order_date') {
            $ordered = (string) ($order['ordered_at'] ?? '');
            if ($ordered !== '') {
                $ts = strtotime($ordered);
                return $ts !== false ? date('Y-m-d H:i:s', $ts) : $issueDate;
            }
        }

        if ($source === 'payment_date' && $payments !== []) {
            $lastPayment = $payments[0] ?? [];
            $payDate = (string) ($lastPayment['payment_date'] ?? '');
            if ($payDate !== '') {
                $ts = strtotime($payDate);
                return $ts !== false ? date('Y-m-d H:i:s', $ts) : $issueDate;
            }
        }

        return $issueDate;
    }

    /**
     * @param array<string, mixed> $config
     * @param array<string, mixed> $order
     */
    private function resolveOrderReference(array $config, array $order): ?string
    {
        $ref = (string) ($config['order_reference'] ?? 'none');

        if ($ref === 'orderpro') {
            return (string) ($order['internal_order_number'] ?? '');
        }

        if ($ref === 'integration') {
            return (string) ($order['external_order_id'] ?? '');
        }

        return null;
    }

    /**
     * @return array<string, mixed>
     */
    private function buildSellerSnapshot(): array
    {
        $seller = $this->companySettings->getSettings();

        return [
            'company_name' => $seller['company_name'] ?? '',
            'tax_number' => $seller['tax_number'] ?? '',
            'street' => $seller['street'] ?? '',
            'city' => $seller['city'] ?? '',
            'postal_code' => $seller['postal_code'] ?? '',
            'phone' => $seller['phone'] ?? '',
            'email' => $seller['email'] ?? '',
            'bank_account' => $seller['bank_account'] ?? '',
            'bdo_number' => $seller['bdo_number'] ?? '',
            'regon' => $seller['regon'] ?? '',
            'court_register' => $seller['court_register'] ?? '',
        ];
    }

    /**
     * @param list<array<string, mixed>> $addresses
     * @return array<string, mixed>|null
     */
    private function buildBuyerSnapshot(array $addresses): ?array
    {
        $buyerAddress = $this->resolveBuyerAddress($addresses);
        if ($buyerAddress === null) {
            return null;
        }

        return [
            'name' => $buyerAddress['name'] ?? '',
            'company_name' => $buyerAddress['company_name'] ?? '',
            'tax_number' => $buyerAddress['company_tax_number'] ?? '',
            'street' => trim(($buyerAddress['street_name'] ?? '') . ' ' . ($buyerAddress['street_number'] ?? '')),
            'city' => $buyerAddress['city'] ?? '',
            'postal_code' => $buyerAddress['zip_code'] ?? '',
            'phone' => $buyerAddress['phone'] ?? '',
            'email' => $buyerAddress['email'] ?? '',
        ];
    }

    /**
     * @param list<array<string, mixed>> $addresses
     * @return array<string, mixed>|null
     */
    private function resolveBuyerAddress(array $addresses): ?array
    {
        $byType = [];
        foreach ($addresses as $addr) {
            $type = (string) ($addr['address_type'] ?? '');
            if ($type !== '' && !isset($byType[$type])) {
                $byType[$type] = $addr;
            }
        }

        return $byType['invoice'] ?? $byType['customer'] ?? null;
    }

    /**
     * @param list<array<string, mixed>> $items
     * @param array<string, mixed> $order
     * @return array{items: list<array<string, mixed>>, total_gross: float}
     */
    private function buildItemsSnapshot(array $items, array $order): array
    {
        $itemsSnapshot = [];
        $totalGross = 0.0;
        foreach ($items as $item) {
            $qty = (float) ($item['quantity'] ?? 0);
            $price = $item['original_price_with_tax'] !== null ? (float) $item['original_price_with_tax'] : 0.0;
            $lineTotal = $qty * $price;
            $totalGross += $lineTotal;
            $itemsSnapshot[] = [
                'name' => $item['original_name'] ?? '',
                'quantity' => $qty,
                'price' => $price,
                'total' => $lineTotal,
                'sku' => $item['sku'] ?? '',
                'ean' => $item['ean'] ?? '',
            ];
        }

        $deliveryPrice = (float) ($order['delivery_price'] ?? 0);
        if ($deliveryPrice > 0) {
            $totalGross += $deliveryPrice;
            $itemsSnapshot[] = [
                'name' => 'Koszt wysylki',
                'quantity' => 1.0,
                'price' => $deliveryPrice,
                'total' => $deliveryPrice,
                'sku' => '',
                'ean' => '',
            ];
        }

        return [
            'items' => $itemsSnapshot,
            'total_gross' => $totalGross,
        ];
    }
}
