<?php

namespace App\Modules\Invoice\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Responses\ApiResponse;
use App\Modules\Invoice\Models\Customer;
use App\Modules\Invoice\Models\Invoice;
use App\Modules\Invoice\Requests\StoreInvoiceRequest;
use App\Modules\Invoice\Requests\UpdateInvoiceRequest;
use App\Modules\Invoice\Resources\InvoiceResource;
use App\Modules\Tenant\Models\Company;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;

class InvoiceController extends Controller
{
    public function index(Request $request): JsonResponse
    {
        $company = $this->currentCompany($request);
        $query = $this->filteredInvoicesQuery($request, $company);

        if ($request->hasAny(['page', 'per_page'])) {
            $perPage = min(max((int) $request->query('per_page', 15), 1), 100);
            $paginator = $query->paginate($perPage);

            return ApiResponse::success([
                'data' => InvoiceResource::collection($paginator->items()),
                'meta' => [
                    'current_page' => $paginator->currentPage(),
                    'from' => $paginator->firstItem(),
                    'last_page' => $paginator->lastPage(),
                    'per_page' => $paginator->perPage(),
                    'to' => $paginator->lastItem(),
                    'total' => $paginator->total(),
                ],
            ]);
        }

        return ApiResponse::success(InvoiceResource::collection($query->get()));
    }

    /**
     * @return Builder<Invoice>
     */
    private function filteredInvoicesQuery(Request $request, Company $company): Builder
    {
        return Invoice::query()
            ->where('company_id', $company->id)
            ->with('customer')
            ->when($request->query('search'), function ($query, mixed $search): void {
                $term = '%'.(string) $search.'%';
                $query->where(function ($innerQuery) use ($term): void {
                    $innerQuery
                        ->where('invoice_number', 'like', $term)
                        ->orWhereHas('customer', function ($customerQuery) use ($term): void {
                            $customerQuery
                                ->where('name', 'like', $term)
                                ->orWhere('email', 'like', $term)
                                ->orWhere('phone', 'like', $term);
                        });
                });
            })
            ->when($request->query('status'), fn ($query, mixed $status) => $query->where('status', (string) $status))
            ->when($request->query('date_from'), fn ($query, mixed $date) => $query->whereDate('issue_date', '>=', (string) $date))
            ->when($request->query('date_to'), fn ($query, mixed $date) => $query->whereDate('issue_date', '<=', (string) $date))
            ->latest();
    }

    public function store(StoreInvoiceRequest $request): JsonResponse
    {
        $company = $this->currentCompany($request);
        $data = $request->validated();

        if (! empty($data['customer_id']) && ! Customer::query()
            ->where('company_id', $company->id)
            ->whereKey($data['customer_id'])
            ->exists()) {
            return ApiResponse::error('Customer not found', 404);
        }

        $invoice = DB::transaction(function () use ($company, $data): Invoice {
            $totals = $this->calculateTotals($company, $data['items']);
            $issueDate = $data['issue_date'];
            $dueDate = $data['due_date'] ?? $this->defaultDueDate($company, $issueDate);

            $invoice = Invoice::query()->create([
                'company_id' => $company->id,
                'customer_id' => $data['customer_id'] ?? null,
                'invoice_number' => $this->nextInvoiceNumber($company),
                'issue_date' => $issueDate,
                'due_date' => $dueDate,
                'subtotal' => $totals['subtotal'],
                'tax_total' => $totals['tax_total'],
                'discount_total' => $totals['discount_total'],
                'total' => $totals['total'],
                'paid_total' => 0,
                'due_total' => $totals['total'],
                'status' => $data['status'] ?? 'draft',
                'notes' => $data['notes'] ?? null,
            ]);

            foreach ($totals['items'] as $item) {
                $invoice->items()->create($item);
            }

            return $invoice;
        });

        return ApiResponse::success(
            new InvoiceResource($invoice->load(['customer', 'items', 'payments.financialAccount'])),
            'Invoice created successfully',
            201
        );
    }

    public function show(Request $request, Invoice $invoice): JsonResponse
    {
        $company = $this->currentCompany($request);

        if ((int) $invoice->company_id !== (int) $company->id) {
            return ApiResponse::error('Invoice not found', 404);
        }

        return ApiResponse::success(
            new InvoiceResource($invoice->load(['customer', 'items', 'payments.financialAccount']))
        );
    }

    public function update(UpdateInvoiceRequest $request, Invoice $invoice): JsonResponse
    {
        $company = $this->currentCompany($request);

        if ((int) $invoice->company_id !== (int) $company->id) {
            return ApiResponse::error('Invoice not found', 404);
        }

        $data = $request->validated();

        if (($data['status'] ?? null) === 'cancelled' && (float) $invoice->paid_total > 0) {
            return ApiResponse::error('Paid invoice cannot be cancelled', 422);
        }

        if (($data['status'] ?? null) === 'cancelled') {
            $data['due_total'] = 0;
        }

        if (in_array($data['status'] ?? null, ['draft', 'sent'], true) && (float) $invoice->paid_total <= 0) {
            $data['due_total'] = $invoice->total;
        }

        $invoice->update($data);

        return ApiResponse::success(
            new InvoiceResource($invoice->fresh()->load(['customer', 'items', 'payments.financialAccount'])),
            'Invoice updated successfully'
        );
    }

    public function exportHtml(Request $request, Invoice $invoice): Response
    {
        $company = $this->currentCompany($request);

        if ((int) $invoice->company_id !== (int) $company->id) {
            return response('Invoice not found', 404);
        }

        $filename = preg_replace('/[^A-Za-z0-9_-]/', '-', $invoice->invoice_number).'.html';

        return response()
            ->view('invoices.export', [
                'company' => $company,
                'invoice' => $invoice->load(['customer', 'items']),
            ])
            ->header('Content-Type', 'text/html; charset=UTF-8')
            ->header('Content-Disposition', 'inline; filename="'.$filename.'"');
    }

    /**
     * @param list<array<string, mixed>> $items
     * @return array{subtotal: float, tax_total: float, discount_total: float, total: float, items: list<array<string, mixed>>}
     */
    private function calculateTotals(Company $company, array $items): array
    {
        $preparedItems = [];
        $subtotal = 0.0;
        $taxTotal = 0.0;
        $discountTotal = 0.0;
        $defaultTaxRate = $this->settingFloat($company, 'default_tax_rate', 0);

        foreach ($items as $item) {
            $quantity = (float) $item['quantity'];
            $unitPrice = (float) $item['unit_price'];
            $taxRate = array_key_exists('tax_rate', $item) && $item['tax_rate'] !== null
                ? (float) $item['tax_rate']
                : $defaultTaxRate;
            $discountAmount = (float) ($item['discount_amount'] ?? 0);
            $lineSubtotal = round($quantity * $unitPrice, 2);
            $taxAmount = round(max($lineSubtotal - $discountAmount, 0) * ($taxRate / 100), 2);
            $lineTotal = round(max($lineSubtotal - $discountAmount, 0) + $taxAmount, 2);

            $subtotal += $lineSubtotal;
            $taxTotal += $taxAmount;
            $discountTotal += $discountAmount;

            $preparedItems[] = [
                'description' => $item['description'],
                'quantity' => $quantity,
                'unit_price' => $unitPrice,
                'tax_rate' => $taxRate,
                'tax_amount' => $taxAmount,
                'discount_amount' => $discountAmount,
                'line_total' => $lineTotal,
            ];
        }

        return [
            'subtotal' => round($subtotal, 2),
            'tax_total' => round($taxTotal, 2),
            'discount_total' => round($discountTotal, 2),
            'total' => round($subtotal - $discountTotal + $taxTotal, 2),
            'items' => $preparedItems,
        ];
    }

    private function nextInvoiceNumber(Company $company): string
    {
        $next = Invoice::query()
            ->where('company_id', $company->id)
            ->count() + 1;

        return $this->settingPrefix($company, 'invoice_prefix', 'INV').'-'.now()->format('Y').'-'.str_pad((string) $next, 6, '0', STR_PAD_LEFT);
    }

    private function defaultDueDate(Company $company, string $issueDate): ?string
    {
        $paymentTermsDays = (int) ($company->settings['payment_terms_days'] ?? 0);

        if ($paymentTermsDays <= 0) {
            return null;
        }

        return \Carbon\CarbonImmutable::parse($issueDate)->addDays($paymentTermsDays)->toDateString();
    }

    private function settingFloat(Company $company, string $key, float $default): float
    {
        return (float) ($company->settings[$key] ?? $default);
    }

    private function settingPrefix(Company $company, string $key, string $default): string
    {
        $prefix = strtoupper((string) ($company->settings[$key] ?? $default));

        return $prefix !== '' ? $prefix : $default;
    }

    private function currentCompany(Request $request): Company
    {
        return $request->attributes->get('currentCompany');
    }
}
