<?php

namespace App\Services;

use App\Enums\InvoiceStatus;
use App\Enums\InvoiceType;
use App\Enums\PaymentMethod;
use App\Models\Customer;
use App\Models\Invoice;
use App\Models\Line;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

class InvoiceService
{
    public function __construct(
        protected NumberService $numbers,
        protected AuditLogService $audit
    ) {
    }

    public function create(array $payload, User $user): Invoice
    {
        return DB::transaction(function () use ($payload, $user) {
            $items = $payload['items'] ?? [];
            $amount = '0.00';
            foreach ($items as $item) {
                $amount = bcadd($amount, (string) ($item['total'] ?? $item['unit_price'] ?? 0), 2);
            }

            $discount = (string) ($payload['discount'] ?? '0.00');
            $tax = (string) ($payload['tax'] ?? '0.00');
            $total = bcadd(bcsub($amount, $discount, 2), $tax, 2);
            $paid = (string) ($payload['paid'] ?? '0.00');
            $remaining = bcsub($total, $paid, 2);
            $status = $this->statusFromTotals($total, $paid);

            $invoice = Invoice::query()->create([
                'invoice_number' => $this->numbers->invoice(),
                'invoice_type' => $payload['invoice_type'] ?? InvoiceType::Billing->value,
                'customer_id' => $payload['customer_id'],
                'line_id' => $payload['line_id'] ?? null,
                'main_account_id' => $payload['main_account_id'] ?? null,
                'sub_account_id' => $payload['sub_account_id'] ?? null,
                'plan_id' => $payload['plan_id'] ?? null,
                'billing_period_id' => $payload['billing_period_id'] ?? null,
                'amount' => $amount,
                'discount' => $discount,
                'tax' => $tax,
                'total' => $total,
                'paid' => $paid,
                'remaining' => $remaining,
                'payment_method' => $payload['payment_method'] ?? null,
                'status' => $status->value,
                'notes' => $payload['notes'] ?? null,
                'date' => $payload['date'] ?? now()->toDateString(),
                'user_id' => $user->id,
            ]);

            foreach ($items as $item) {
                $invoice->items()->create([
                    'item_type' => $item['item_type'] ?? 'service',
                    'description' => $item['description'],
                    'billing_period_id' => $item['billing_period_id'] ?? null,
                    'plan_id' => $item['plan_id'] ?? null,
                    'line_id' => $item['line_id'] ?? null,
                    'quantity' => $item['quantity'] ?? 1,
                    'unit_price' => $item['unit_price'],
                    'discount' => $item['discount'] ?? 0,
                    'tax' => $item['tax'] ?? 0,
                    'total' => $item['total'] ?? $item['unit_price'],
                ]);
            }

            $this->audit->record('invoice.created', $invoice, [], $invoice->toArray(), $user->id);

            return $invoice->load('items');
        });
    }

    public function applyPayment(Invoice $invoice, string $amount): Invoice
    {
        $paid = bcadd((string) $invoice->paid, $amount, 2);
        if (bccomp($paid, (string) $invoice->total, 2) === 1) {
            $paid = (string) $invoice->total;
        }
        $remaining = bcsub((string) $invoice->total, $paid, 2);
        $invoice->update([
            'paid' => $paid,
            'remaining' => $remaining,
            'status' => $this->statusFromTotals((string) $invoice->total, $paid)->value,
        ]);

        return $invoice->refresh();
    }

    public function cancel(Invoice $invoice, User $user, string $reason): Invoice
    {
        if (in_array($invoice->status, [InvoiceStatus::Cancelled, InvoiceStatus::Void], true)) {
            throw ValidationException::withMessages([
                'invoice' => __('messages.invoice_already_cancelled'),
            ]);
        }

        return DB::transaction(function () use ($invoice, $user, $reason) {
            $old = $invoice->status->value;
            $invoice->update([
                'status' => InvoiceStatus::Cancelled->value,
                'cancelled_reason' => $reason,
                'cancelled_at' => now(),
                'cancelled_by' => $user->id,
            ]);

            $invoice->items()
                ->whereNotNull('billing_period_id')
                ->each(function ($item) use ($invoice) {
                    if ($item->line_id) {
                        $invoice->line?->charges()
                            ->where('billing_period_id', $item->billing_period_id)
                            ->where('invoice_id', $invoice->id)
                            ->update(['status' => 'unpaid', 'invoice_id' => null]);
                    }
                });

            $this->audit->record('invoice.cancelled', $invoice, ['status' => $old], ['status' => 'cancelled', 'reason' => $reason], $user->id);

            return $invoice->refresh();
        });
    }

    public function update(Invoice $invoice, array $data, User $user): Invoice
    {
        $old = $invoice->only(['notes', 'date']);
        $invoice->update([
            'notes' => $data['notes'] ?? $invoice->notes,
            'date' => $data['date'] ?? $invoice->date,
        ]);

        $this->audit->record('invoice.updated', $invoice, $old, $invoice->only(['notes', 'date']), $user->id);

        return $invoice->refresh();
    }

    public function delete(Invoice $invoice, User $user, string $reason): void
    {
        DB::transaction(function () use ($invoice, $user, $reason) {
            if (! in_array($invoice->status, [InvoiceStatus::Cancelled, InvoiceStatus::Void], true)) {
                $this->cancel($invoice, $user, $reason);
                $invoice->refresh();
            }

            $this->audit->record('invoice.deleted', $invoice, [
                'invoice_number' => $invoice->invoice_number,
                'status' => $invoice->status->value,
            ], ['reason' => $reason], $user->id);

            $invoice->delete();
        });
    }

    public function shareText(Invoice $invoice): string
    {
        $invoice->loadMissing(['customer', 'line', 'items']);
        $company = (string) setting('company_name', config('app.name'));

        $lines = array_filter([
            $company,
            __('messages.invoice').': '.$invoice->invoice_number,
            __('messages.date').': '.optional($invoice->date)->toDateString(),
            $invoice->customer?->name ? __('messages.customer').': '.$invoice->customer->name : null,
            $invoice->customer?->phone ? __('messages.phone').': '.$invoice->customer->phone : null,
            $invoice->line?->phone_number ? __('messages.line').': '.$invoice->line->phone_number : null,
        ]);

        foreach ($invoice->items as $item) {
            $lines[] = '- '.$item->description.' '.money($item->total);
        }

        $lines[] = __('messages.total').': '.money($invoice->total);
        $lines[] = __('messages.paid').': '.money($invoice->paid);
        $lines[] = __('messages.remaining').': '.money($invoice->remaining);

        if ($invoice->notes) {
            $lines[] = __('messages.notes').': '.$invoice->notes;
        }

        return implode("\n", $lines);
    }

    public function statusFromTotals(string $total, string $paid): InvoiceStatus
    {
        if (bccomp($paid, '0.00', 2) === 0) {
            return InvoiceStatus::Active;
        }
        if (bccomp($paid, $total, 2) >= 0) {
            return InvoiceStatus::Paid;
        }

        return InvoiceStatus::PartiallyPaid;
    }
}
