<?php
declare(strict_types=1);

namespace App\Modules\Accounting;

use App\Core\Http\Request;
use App\Core\Http\Response;
use App\Core\I18n\Translator;
use App\Core\Security\Csrf;
use App\Core\Support\Flash;
use App\Core\View\Template;
use App\Modules\Auth\AuthService;
use App\Modules\Automation\AutomationService;
use App\Modules\Orders\OrdersRepository;
use App\Modules\Settings\CompanySettingsRepository;
use App\Modules\Settings\ReceiptConfigRepository;
use Throwable;

final class ReceiptController
{
    public function __construct(
        private readonly Template $template,
        private readonly Translator $translator,
        private readonly AuthService $auth,
        private readonly ReceiptRepository $receipts,
        private readonly ReceiptConfigRepository $receiptConfigs,
        private readonly CompanySettingsRepository $companySettings,
        private readonly OrdersRepository $orders,
        private readonly AutomationService $automation
    ) {
    }

    public function create(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        $details = $this->orders->findDetails($orderId);
        if ($details === null) {
            return Response::html('Not found', 404);
        }

        $configs = array_filter($this->receiptConfigs->listAll(), static fn(array $c): bool => (int) ($c['is_active'] ?? 0) === 1);
        if ($configs === []) {
            Flash::set('order.error', $this->translator->get('receipts.create.no_configs'));
            return Response::redirect('/orders/' . $orderId);
        }

        $existingReceipts = $this->receipts->findByOrderId($orderId);

        $order = is_array($details['order'] ?? null) ? $details['order'] : [];
        $items = is_array($details['items'] ?? null) ? $details['items'] : [];
        $seller = $this->companySettings->getSettings();

        $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;
            $totalGross += $qty * $price;
        }

        $html = $this->template->render('orders/receipt-create', [
            'title' => $this->translator->get('receipts.create.title'),
            'activeMenu' => 'orders',
            'activeOrders' => 'list',
            'user' => $this->auth->user(),
            'csrfToken' => Csrf::token(),
            'orderId' => $orderId,
            'order' => $order,
            'items' => $items,
            'configs' => array_values($configs),
            'seller' => $seller,
            'totalGross' => $totalGross,
            'existingReceipts' => $existingReceipts,
        ], 'layouts/app');

        return Response::html($html);
    }

    public function store(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));

        if (!Csrf::validate((string) $request->input('_token', ''))) {
            Flash::set('order.error', 'Nieprawidlowy token CSRF');
            return Response::redirect('/orders/' . $orderId);
        }

        $configId = (int) $request->input('config_id', '0');
        if ($configId <= 0) {
            Flash::set('order.error', $this->translator->get('receipts.create.no_config_selected'));
            return Response::redirect('/orders/' . $orderId . '/receipt/create');
        }

        $config = $this->receiptConfigs->findById($configId);
        if ($config === null || (int) ($config['is_active'] ?? 0) !== 1) {
            Flash::set('order.error', $this->translator->get('receipts.create.invalid_config'));
            return Response::redirect('/orders/' . $orderId . '/receipt/create');
        }

        $details = $this->orders->findDetails($orderId);
        if ($details === null) {
            return Response::html('Not found', 404);
        }

        $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'] : [];

        $seller = $this->companySettings->getSettings();
        $sellerSnapshot = [
            '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'] ?? '',
        ];

        $buyerAddress = $this->resolveBuyerAddress($addresses);
        $buyerSnapshot = $buyerAddress !== null ? [
            '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'] ?? '',
        ] : null;

        $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'] ?? '',
            ];
        }

        $issueDate = trim((string) $request->input('issue_date', ''));
        if ($issueDate === '' || strtotime($issueDate) === false) {
            $issueDate = date('Y-m-d');
        }

        $saleDate = $this->resolveSaleDate($config, $order, $payments, $issueDate);

        $orderReference = $this->resolveOrderReference($config, $order);

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

            $user = $this->auth->user();

            $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' => is_array($user) ? ($user['id'] ?? null) : null,
            ]);

            $userName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : '';
            $this->orders->recordActivity(
                $orderId,
                'receipt_issued',
                'Wystawiono paragon: ' . $receiptNumber,
                ['receipt_number' => $receiptNumber, 'config_id' => $configId, 'total_gross' => number_format($totalGross, 2, '.', '')],
                'user',
                $userName !== '' ? $userName : null
            );

            Flash::set('order.success', 'Paragon wystawiony: ' . $receiptNumber);

            try {
                $this->automation->trigger('receipt.created', $orderId);
            } catch (Throwable) {
                // Blad automatyzacji nie blokuje sukcesu paragonu
            }
        } catch (Throwable) {
            Flash::set('order.error', 'Blad wystawiania paragonu');
        }

        return Response::redirect('/orders/' . $orderId);
    }

    /**
     * @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 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', $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', $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;
    }

    public function show(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        $receiptId = max(0, (int) $request->input('receiptId', 0));

        $receipt = $this->receipts->findById($receiptId);
        if ($receipt === null || (int) ($receipt['order_id'] ?? 0) !== $orderId) {
            return Response::html('Not found', 404);
        }

        $data = $this->buildReceiptViewData($receipt, $orderId);

        $html = $this->template->render('receipts/show', $data, 'layouts/app');
        return Response::html($html);
    }

    public function printView(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        $receiptId = max(0, (int) $request->input('receiptId', 0));

        $receipt = $this->receipts->findById($receiptId);
        if ($receipt === null || (int) ($receipt['order_id'] ?? 0) !== $orderId) {
            return Response::html('Not found', 404);
        }

        $data = $this->buildReceiptViewData($receipt, $orderId);

        $html = $this->template->render('receipts/print', $data);
        return Response::html($html);
    }

    public function pdf(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        $receiptId = max(0, (int) $request->input('receiptId', 0));

        $receipt = $this->receipts->findById($receiptId);
        if ($receipt === null || (int) ($receipt['order_id'] ?? 0) !== $orderId) {
            return Response::html('Not found', 404);
        }

        $data = $this->buildReceiptViewData($receipt, $orderId);

        $html = $this->template->render('receipts/print', $data);

        $dompdf = new \Dompdf\Dompdf();
        $dompdf->loadHtml($html);
        $dompdf->setPaper('A4');
        $dompdf->render();

        $filename = str_replace(['/', '\\'], '_', (string) ($receipt['receipt_number'] ?? 'receipt')) . '.pdf';

        return new Response($dompdf->output() ?: '', 200, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'attachment; filename="' . $filename . '"',
        ]);
    }

    /**
     * @param array<string, mixed> $receipt
     * @return array<string, mixed>
     */
    private function buildReceiptViewData(array $receipt, int $orderId): array
    {
        $seller = json_decode((string) ($receipt['seller_data_json'] ?? '{}'), true);
        $buyer = ($receipt['buyer_data_json'] ?? null) !== null
            ? json_decode((string) $receipt['buyer_data_json'], true)
            : null;
        $items = json_decode((string) ($receipt['items_json'] ?? '[]'), true);

        $configName = '';
        $config = $this->receiptConfigs->findById((int) ($receipt['config_id'] ?? 0));
        if ($config !== null) {
            $configName = (string) ($config['name'] ?? '');
        }

        return [
            'title' => $this->translator->get('receipts.show.title') . ' ' . ($receipt['receipt_number'] ?? ''),
            'activeMenu' => 'orders',
            'activeOrders' => 'list',
            'user' => $this->auth->user(),
            'orderId' => $orderId,
            'receipt' => $receipt,
            'seller' => is_array($seller) ? $seller : [],
            'buyer' => is_array($buyer) ? $buyer : null,
            'items' => is_array($items) ? $items : [],
            'configName' => $configName,
        ];
    }
}
