<?php

namespace App\Models;

use App\Enums\FbrInvoiceType;
use App\Enums\InvoiceSyncStatus;
use App\Traits\BelongsToBusiness;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str;

class Invoice extends Model
{
    use HasFactory, SoftDeletes, BelongsToBusiness;

    protected $fillable = [
        'business_id',
        'customer_id',
        'local_invoice_no',
        'invoice_type',
        'invoice_date',
        'original_invoice_id',  // self-reference for Debit/Credit Notes
        'scenario_id',
        'sync_status',
        'sync_attempts',
        'last_synced_at',
        'scheduled_sync_at',
        'fbr_invoice_number',
        'fbr_dated',
        'last_error_code',
        'last_error_message',
        'subtotal',
        'total_tax',
        'total_amount',
        'notes',
        'reason',
        'remarks',
        'withholding_tax_rate',
        'withholding_tax_amount',
    ];

    protected $casts = [
        'invoice_date' => 'date:Y-m-d',
        'last_synced_at' => 'datetime',
        'scheduled_sync_at' => 'datetime',
        'subtotal' => 'decimal:2',
        'total_tax' => 'decimal:2',
        'total_amount' => 'decimal:2',
        'invoice_type' => FbrInvoiceType::class,
        'sync_status' => InvoiceSyncStatus::class,
    ];

    protected static function booted(): void
    {
        static::creating(function (Invoice $invoice) {
            if (empty($invoice->local_invoice_no)) {
                $invoice->local_invoice_no = static::generateLocalNumber();
            }
            if (empty($invoice->sync_status)) {
                $invoice->sync_status = InvoiceSyncStatus::Draft;
            }
        });
    }

    public static function generateLocalNumber(): string
    {
        $prefix = class_exists(\App\Models\Setting::class)
            ? \App\Models\Setting::get('invoice_prefix', 'INV-')
            : 'INV-';
        return $prefix . now()->format('Ymd') . '-' . Str::upper(Str::random(6));
    }

    // ------------------------------------------------------------------
    // Relations
    // ------------------------------------------------------------------

    public function business()
    {
        return $this->belongsTo(Business::class);
    }

    public function customer()
    {
        return $this->belongsTo(Customer::class);
    }

    public function items()
    {
        return $this->hasMany(InvoiceItem::class);
    }

    public function originalInvoice()
    {
        return $this->belongsTo(Invoice::class, 'original_invoice_id');
    }

    public function notes()
    {
        // Debit/Credit notes filed against this invoice
        return $this->hasMany(Invoice::class, 'original_invoice_id');
    }

    public function fbrLogs()
    {
        return $this->hasMany(FbrInvoiceLog::class, 'reference_id')
            ->where('reference_type', static::class);
    }

    // ------------------------------------------------------------------
    // State helpers
    // ------------------------------------------------------------------

    public function isSynced(): bool
    {
        return $this->sync_status === InvoiceSyncStatus::Synced;
    }

    public function canEdit(): bool
    {
        return $this->sync_status->canEdit();
    }

    public function canQueueForSync(): bool
    {
        return $this->sync_status->canQueue();
    }

    public function isNote(): bool
    {
        return in_array($this->invoice_type, [FbrInvoiceType::DebitNote, FbrInvoiceType::CreditNote], true);
    }

    /**
     * Recalculate and persist subtotal/tax/total from current line items.
     * Call after attaching/updating items, before save() if not auto-saved.
     */
    public function recalculateTotals(): void
    {
        $items = $this->items()->get();

        // value_sales_excluding_st is stored as per-unit price; multiply by quantity
        // to get the actual taxable subtotal — matching the frontend's grossSubtotal logic.
        $this->subtotal = $items->sum(fn($item) => (float) $item->value_sales_excluding_st * (float) $item->quantity);
        $this->total_tax = $items->sum('sales_tax_applicable')
            + $items->sum('further_tax')
            + $items->sum('extra_tax')
            + $items->sum('fed_payable')
            - $items->sum('discount');
        $this->total_amount = $this->subtotal + $this->total_tax;
    }

    // ------------------------------------------------------------------
    // FBR payload shaping
    // ------------------------------------------------------------------

    /**
     * Build the array shape expected by FbrDigitalInvoiceService::postInvoice()
     * / validateInvoice() from this DB record + its relations.
     */
    public function toFbrPayload(): array
    {
        $business = $this->business ?? Business::withoutGlobalScopes()->find($this->business_id);

        if (!$business) {
            throw new \App\Exceptions\FbrApiException(
                "Invoice {$this->local_invoice_no} has no associated business — cannot build FBR payload.",
                code: 500,
            );
        }

        $seller = $business->sellerDefaults();
        $buyer = $this->customer?->toFbrBuyerPayload() ?? [];

        $payload = [
            'invoiceType' => $this->invoice_type->value,
            'invoiceDate' => $this->invoice_date->format('Y-m-d'),
            'sellerBusinessName' => $seller['business_name'],
            'sellerNTNCNIC' => $seller['ntn_cnic'],
            'sellerProvince' => $seller['province'],
            'sellerAddress' => $seller['address'],
            'buyerNTNCNIC' => $buyer['buyerNTNCNIC'] ?? null,
            'buyerBusinessName' => $buyer['buyerBusinessName'] ?? null,
            'buyerProvince' => $buyer['buyerProvince'] ?? null,
            'buyerAddress' => $buyer['buyerAddress'] ?? null,
            'invoiceRefNo' => $this->originalInvoice?->fbr_invoice_number ?? '',
        ];

        if ($this->isNote()) {
            $payload['reason'] = $this->reason;
            $payload['remarks'] = $this->remarks;
        }

        if (!empty($this->scenario_id)) {
            $payload['scenarioId'] = $this->scenario_id;
        }

        $payload['buyerRegistrationType'] = $buyer['buyerRegistrationType'] ?? 'Unregistered';

        $payload['items'] = $this->items()->get()->map(fn(InvoiceItem $item) => $item->toFbrPayload())->all();

        return $payload;
    }

    protected function prefixSeller(array $defaults): array
    {
        return [
            'sellerNTNCNIC' => $defaults['ntn_cnic'],
            'sellerBusinessName' => $defaults['business_name'],
            'sellerProvince' => $defaults['province'],
            'sellerAddress' => $defaults['address'],
        ];
    }

    // ------------------------------------------------------------------
    // Scopes
    // ------------------------------------------------------------------

    public function scopeDrafts($query)
    {
        return $query->where('sync_status', InvoiceSyncStatus::Draft);
    }

    public function scopeSynced($query)
    {
        return $query->where('sync_status', InvoiceSyncStatus::Synced);
    }

    public function scopeFailed($query)
    {
        return $query->where('sync_status', InvoiceSyncStatus::Failed);
    }

    public function scopePendingSync($query)
    {
        return $query->whereIn('sync_status', [InvoiceSyncStatus::Queued, InvoiceSyncStatus::Syncing]);
    }
}
