<?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\Orders\OrdersRepository;
use App\Core\Http\MfWhitelistApiClient;
use App\Modules\Settings\CompanySettingsRepository;
use App\Modules\Settings\InvoiceConfigRepository;
use Throwable;

final class InvoiceController
{
    public function __construct(
        private readonly Template $template,
        private readonly Translator $translator,
        private readonly AuthService $auth,
        private readonly InvoiceRepository $invoices,
        private readonly InvoiceConfigRepository $invoiceConfigs,
        private readonly CompanySettingsRepository $companySettings,
        private readonly OrdersRepository $orders,
        private readonly InvoiceService $invoiceService,
        private readonly MfWhitelistApiClient $mfWhitelist
    ) {
    }

    public function nipLookup(Request $request): Response
    {
        $nip = preg_replace('/[\s\-]/', '', (string) $request->input('nip', '')) ?? '';
        if (!preg_match('/^\d{10}$/', $nip)) {
            return Response::json(['success' => false, 'error' => 'Niepoprawny NIP (musi miec 10 cyfr).'], 422);
        }

        try {
            $data = $this->mfWhitelist->lookupByNip($nip);
        } catch (Throwable $e) {
            return Response::json(['success' => false, 'error' => $e->getMessage()], 502);
        }

        return Response::json([
            'success' => true,
            'data' => [
                'company_name' => $data['name'],
                'tax_number' => $data['tax_no'],
                'street' => $data['street'],
                'postal_code' => $data['postal_code'],
                'city' => $data['city'],
                'country' => $data['country'],
                'regon' => $data['regon'],
                'status_vat' => $data['status_vat'],
            ],
        ]);
    }

    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);
        }

        $order = is_array($details['order'] ?? null) ? $details['order'] : [];
        if ((int) ($order['invoice_requested'] ?? 0) !== 1) {
            Flash::set('order.error', 'Faktura nie zostala zazadana dla tego zamowienia.');
            return Response::redirect('/orders/' . $orderId);
        }

        $configs = array_values(array_filter(
            $this->invoiceConfigs->listAll(),
            static fn (array $c): bool => (int) ($c['is_active'] ?? 0) === 1
        ));
        if ($configs === []) {
            Flash::set('order.error', 'Brak aktywnych konfiguracji faktur. Skonfiguruj w Ustawienia > Ksiegowosc > Faktury.');
            return Response::redirect('/orders/' . $orderId);
        }

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

        $byType = [];
        foreach ($addresses as $addr) {
            $type = (string) ($addr['address_type'] ?? '');
            if ($type !== '' && !isset($byType[$type])) {
                $byType[$type] = $addr;
            }
        }
        $buyerAddress = $byType['invoice'] ?? $byType['customer'] ?? null;
        $autoTaxNumber = InvoiceService::extractBuyerTaxNumber($order, $buyerAddress);

        $existingInvoices = $this->invoices->findByOrderId($orderId);

        $html = $this->template->render('accounting/invoice_form', [
            'title' => 'Wystaw fakture',
            'activeMenu' => 'orders',
            'activeOrders' => 'list',
            'user' => $this->auth->user(),
            'csrfToken' => Csrf::token(),
            'orderId' => $orderId,
            'order' => $order,
            'items' => $items,
            'configs' => $configs,
            'seller' => $this->companySettings->getSettings(),
            'buyerAddress' => $buyerAddress,
            'autoTaxNumber' => $autoTaxNumber,
            'existingInvoices' => $existingInvoices,
            'errorMessage' => (string) Flash::get('invoice.error', ''),
        ], '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('invoice.error', 'Wybierz konfiguracje faktury.');
            return Response::redirect('/orders/' . $orderId . '/invoice/create');
        }

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

        try {
            $result = $this->invoiceService->issue([
                'order_id' => $orderId,
                'config_id' => $configId,
                'buyer_tax_number' => (string) $request->input('buyer_tax_number', ''),
                'buyer_name' => (string) $request->input('buyer_name', ''),
                'buyer_company_name' => (string) $request->input('buyer_company_name', ''),
                'buyer_street' => (string) $request->input('buyer_street', ''),
                'buyer_city' => (string) $request->input('buyer_city', ''),
                'buyer_postal_code' => (string) $request->input('buyer_postal_code', ''),
                'buyer_email' => (string) $request->input('buyer_email', ''),
                'issue_date_override' => (string) $request->input('issue_date', ''),
                'created_by' => is_array($user) ? ($user['id'] ?? null) : null,
            ]);

            $userName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : '';
            $this->orders->recordActivity(
                $orderId,
                'invoice_issued',
                ($result['mode'] === 'delegated' ? 'Wystawiono fakture (Fakturownia): ' : 'Wystawiono fakture: ') . $result['invoice_number'],
                [
                    'invoice_number' => $result['invoice_number'],
                    'config_id' => $configId,
                    'mode' => $result['mode'],
                    'total_gross' => $result['total_gross'],
                ],
                'user',
                $userName !== '' ? $userName : null
            );

            Flash::set('order.success', 'Faktura wystawiona: ' . $result['invoice_number']);
            return Response::redirect('/orders/' . $orderId . '/invoice/' . $result['invoice_id']);
        } catch (InvoiceIssueException $e) {
            Flash::set('invoice.error', $e->getMessage());
        } catch (Throwable $e) {
            Flash::set('invoice.error', 'Blad wystawiania faktury: ' . $e->getMessage());
        }

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

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

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

        $data = $this->buildInvoiceViewData($invoice, $orderId);
        $html = $this->template->render('accounting/invoice_preview', $data, 'layouts/app');
        return Response::html($html);
    }

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

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

        $externalPdfUrl = trim((string) ($invoice['external_pdf_url'] ?? ''));
        if ($externalPdfUrl !== '') {
            return Response::redirect($externalPdfUrl);
        }

        $data = $this->buildInvoiceViewData($invoice, $orderId);
        $html = $this->template->render('accounting/invoice_pdf', $data);

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

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

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

    public function issuedList(Request $request): Response
    {
        $filters = [
            'search' => trim((string) $request->input('search', '')),
            'config_id' => (int) $request->input('config_id', 0),
            'mode' => (string) $request->input('mode', ''),
            'date_from' => trim((string) $request->input('date_from', '')),
            'date_to' => trim((string) $request->input('date_to', '')),
            'page' => max(1, (int) $request->input('page', 1)),
            'per_page' => 50,
        ];

        $result = $this->invoices->paginate($filters);
        $totalPages = max(1, (int) ceil(((int) $result['total']) / max(1, (int) $result['per_page'])));

        $configs = $this->invoiceConfigs->listAll();

        $html = $this->template->render('accounting/invoices_issued_list', [
            'title' => 'Wystawione faktury',
            'activeMenu' => 'settings',
            'activeSettings' => 'accounting',
            'user' => $this->auth->user(),
            'csrfToken' => Csrf::token(),
            'filters' => $filters,
            'invoices' => $result['items'],
            'total' => $result['total'],
            'page' => $result['page'],
            'perPage' => $result['per_page'],
            'totalPages' => $totalPages,
            'configs' => $configs,
        ], 'layouts/app');

        return Response::html($html);
    }

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

        $configName = trim((string) ($invoice['config_name'] ?? ''));
        $isDelegated = (int) ($invoice['config_is_delegated'] ?? 0) === 1
            || trim((string) ($invoice['external_invoice_id'] ?? '')) !== '';

        return [
            'title' => 'Faktura ' . ($invoice['invoice_number'] ?? ''),
            'activeMenu' => 'orders',
            'activeOrders' => 'list',
            'user' => $this->auth->user(),
            'orderId' => $orderId,
            'invoice' => $invoice,
            'seller' => is_array($seller) ? $seller : [],
            'buyer' => is_array($buyer) ? $buyer : null,
            'items' => $items,
            'configName' => $configName,
            'isDelegated' => $isDelegated,
            'integrationName' => trim((string) ($invoice['integration_name'] ?? '')),
            'accountPrefix' => trim((string) ($invoice['account_prefix'] ?? '')),
        ];
    }
}
