<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use App\Traits\HotelScopeTrait;
use App\Traits\GeneratesUniqueNumbers;
use Illuminate\Support\Facades\Auth;

class Invoice extends Model
{
    use HotelScopeTrait, GeneratesUniqueNumbers;

    protected function getNumberPrefix(): string
    {
        return 'INV';
    }

    protected function getNumberColumn(): string
    {
        return 'invoice_number';
    }

    protected $fillable = [
        'hotel_id', // ADDED
        'invoice_number',
        'type',
        'date',
        'due_date',
        'contact_type',
        'contact_id',
        'total_amount',
        'subtotal',
        'tax_amount',
        'amount_paid',
        'balance_due',
        'status',
        'module_source',
        'source_id',
        'payment_status', // Added
        'reservation_id', // Added for reservation linking
        'service_request_id', // Added for service request linking
        'installment_plan_id', // Added for installment plan reference
        'notes', // Added for additional notes
        'terms', // Added for payment terms
        'currency', // Added for multi-currency support
        'exchange_rate', // Added for currency conversion
        'discount_amount', // Added for discounts
        'subtotal' // Added for better calculation clarity
    ];

    protected $casts = [
        'date' => 'date',
        'due_date' => 'date',
        'total_amount' => 'decimal:2',
        'subtotal' => 'decimal:2',
        'tax_amount' => 'decimal:2',
        'amount_paid' => 'decimal:2',
        'balance_due' => 'decimal:2',
        'discount_amount' => 'decimal:2', // Added
        'exchange_rate' => 'decimal:6', // Added
        'payment_status' => 'string', // Added
        'created_at' => 'datetime',
        'updated_at' => 'datetime'
    ];

    /**
     * The accessors to append to the model's array form.
     *
     * @var array
     */
    protected $appends = [
        'balance',
        'is_overdue',
        'is_partially_paid',
        'is_fully_paid',
        'payment_progress',
        'formatted_total_amount',
        'formatted_tax_amount',
        'formatted_balance',
        'formatted_due_date',
        'formatted_date',
        'days_overdue',
        'contact_name',
        'contact_email',
        'contact_phone',
        'has_installment_payment',
        'installment_details'
    ];

    /**
     * Boot the model.
     */
    protected static function boot()
    {
        parent::boot();
        
        static::creating(function ($invoice) {
            if (empty($invoice->invoice_number)) {
                $invoice->invoice_number = static::generateInvoiceNumber($invoice->type, $invoice->hotel_id);
            }
            
            if (empty($invoice->payment_status)) {
                $invoice->payment_status = 'pending';
            }
            
            if (empty($invoice->currency)) {
                $invoice->currency = setting('base_currency', setting('currency_symbol', 'UGX'));
            }
            
            if (empty($invoice->exchange_rate)) {
                $invoice->exchange_rate = 1;
            }
            
            if (empty($invoice->subtotal) && !empty($invoice->total_amount)) {
                $invoice->subtotal = $invoice->total_amount - ($invoice->tax_amount ?? 0) - ($invoice->discount_amount ?? 0);
            }
            
        });

        static::saved(function ($invoice) {
            // Create journal entry for accounting
            if ($invoice->status === 'paid' && $invoice->wasChanged('status')) {
                $invoice->createJournalEntry();
            }
        });
    }

    /**
     * Get the hotel that owns the invoice.
     */
    public function hotel(): BelongsTo
    {
        return $this->belongsTo(Hotel::class);
    }

    /**
     * Get the contact associated with the invoice.
     */
    public function contact(): MorphTo
    {
        return $this->morphTo('contact', 'contact_type', 'contact_id');
    }

    /**
     * Get the reservation associated with this invoice.
     */
    public function reservation(): BelongsTo
    {
        return $this->belongsTo(Reservation::class, 'reservation_id');
    }

    /**
     * Get the installment plan associated with this invoice.
     */
    public function installmentPlan(): BelongsTo
    {
        return $this->belongsTo(InstallmentPlan::class, 'installment_plan_id');
    }

    /**
     * Get the installment payment associated with this invoice.
     */
    public function installmentPayment(): HasOne
    {
        return $this->hasOne(InstallmentPayment::class);
    }

    /**
     * Get the payments for this invoice.
     */
    public function payments(): HasMany
    {
        return $this->hasMany(Payment::class);
    }

    /**
     * Get the invoice items.
     */
    public function items(): HasMany
    {
        return $this->hasMany(InvoiceItem::class);
    }

    /**
     * Get the journal entries associated with this invoice.
     */
    public function journalEntries()
    {
        return $this->morphMany(JournalEntry::class, 'reference');
    }

    /**
     * Get the tax records associated with this invoice.
     */
    public function taxes()
    {
        return $this->hasMany(InvoiceTax::class);
    }

    /**
     * Scope a query to only include sales invoices.
     */
    public function scopeSales($query)
    {
        return $query->where('type', 'sales');
    }

    /**
     * Scope a query to only include purchase invoices.
     */
    public function scopePurchase($query)
    {
        return $query->where('type', 'purchase');
    }

    /**
     * Scope a query to only include service invoices.
     */
    public function scopeService($query)
    {
        return $query->where('type', 'service');
    }

    /**
     * Scope a query to only include room charge invoices.
     */
    public function scopeRoomCharge($query)
    {
        return $query->where('type', 'room_charge');
    }

    /**
     * Scope a query to only include restaurant invoices.
     */
    public function scopeRestaurant($query)
    {
        return $query->where('type', 'restaurant');
    }

    /**
     * Scope a query to only include pending invoices.
     */
    public function scopePending($query)
    {
        return $query->whereIn('status', ['draft', 'sent']);
    }

    /**
     * Scope a query to only include overdue invoices.
     */
    public function scopeOverdue($query)
    {
        return $query->where('status', 'sent')
                    ->where('due_date', '<', now());
    }

    /**
     * Scope a query to only include paid invoices.
     */
    public function scopePaid($query)
    {
        return $query->where('payment_status', 'paid');
    }

    /**
     * Scope a query to only include partially paid invoices.
     */
    public function scopePartiallyPaid($query)
    {
        return $query->where('payment_status', 'partially_paid');
    }

    /**
     * Scope a query to only include unpaid invoices.
     */
    public function scopeUnpaid($query)
    {
        return $query->where('payment_status', 'pending');
    }

    /**
     * Scope a query to only include invoices by module.
     */
    public function scopeByModule($query, $module)
    {
        return $query->where('module_source', $module);
    }

    /**
     * Scope a query to only include invoices with installment plans.
     */
    public function scopeWithInstallment($query)
    {
        return $query->whereNotNull('installment_plan_id');
    }

    /**
     * Scope a query to only include invoices without installment plans.
     */
    public function scopeWithoutInstallment($query)
    {
        return $query->whereNull('installment_plan_id');
    }

    /**
     * Scope a query to only include invoices for a specific reservation.
     */
    public function scopeForReservation($query, $reservationId)
    {
        return $query->where('reservation_id', $reservationId)
                    ->where('type', 'room_charge');
    }

    /**
     * Get the balance amount (total - payments).
     */
    public function getBalanceAttribute()
    {
        $paid = $this->payments()->whereIn('status', ['completed', 'paid'])->sum('amount');
        return max(0, $this->total_amount - $paid);
    }

    /**
     * Get formatted total amount.
     */
    public function getFormattedTotalAmountAttribute()
    {
        $currency = $this->currency ?: setting('base_currency', setting('currency_symbol', 'UGX'));
        $symbol = $this->getCurrencySymbol($currency);
        return $symbol . number_format($this->total_amount, 2);
    }

    /**
     * Get formatted tax amount.
     */
    public function getFormattedTaxAmountAttribute()
    {
        $currency = $this->currency ?: setting('base_currency', setting('currency_symbol', 'UGX'));
        $symbol = $this->getCurrencySymbol($currency);
        return $symbol . number_format($this->tax_amount, 2);
    }

    /**
     * Get formatted balance.
     */
    public function getFormattedBalanceAttribute()
    {
        $currency = $this->currency ?: setting('base_currency', setting('currency_symbol', 'UGX'));
        $symbol = $this->getCurrencySymbol($currency);
        return $symbol . number_format($this->balance, 2);
    }

    /**
     * Get formatted due date.
     */
    public function getFormattedDueDateAttribute()
    {
        return $this->due_date ? $this->due_date->format('M d, Y') : null;
    }

    /**
     * Get formatted invoice date.
     */
    public function getFormattedDateAttribute()
    {
        return $this->date ? $this->date->format('M d, Y') : null;
    }

    /**
     * Check if invoice is overdue.
     */
    public function getIsOverdueAttribute()
    {
        return $this->status === 'sent' && 
               $this->due_date < now() && 
               $this->balance > 0;
    }

    /**
     * Check if invoice is partially paid.
     */
    public function getIsPartiallyPaidAttribute()
    {
        return $this->payment_status === 'partially_paid';
    }

    /**
     * Check if invoice is fully paid.
     */
    public function getIsFullyPaidAttribute()
    {
        return $this->payment_status === 'paid' || $this->balance <= 0;
    }

    /**
     * Get payment progress percentage.
     */
    public function getPaymentProgressAttribute()
    {
        if ($this->total_amount <= 0) {
            return 100;
        }
        
        $paid = $this->total_amount - $this->balance;
        return ($paid / $this->total_amount) * 100;
    }

    /**
     * Get days overdue.
     */
    public function getDaysOverdueAttribute()
    {
        if (!$this->is_overdue) {
            return 0;
        }
        
        return now()->diffInDays($this->due_date);
    }

    /**
     * Get contact name.
     */
    public function getContactNameAttribute()
    {
        if ($this->contact) {
            if (method_exists($this->contact, 'full_name')) {
                return $this->contact->full_name;
            } elseif (method_exists($this->contact, 'name')) {
                return $this->contact->name;
            }
        }
        
        return 'Unknown Contact';
    }

    /**
     * Get contact email.
     */
    public function getContactEmailAttribute()
    {
        if ($this->contact && property_exists($this->contact, 'email')) {
            return $this->contact->email;
        }
        
        return null;
    }

    /**
     * Get contact phone.
     */
    public function getContactPhoneAttribute()
    {
        if ($this->contact && property_exists($this->contact, 'phone')) {
            return $this->contact->phone;
        }
        
        return null;
    }

    /**
     * Check if invoice has associated installment payment.
     */
    public function getHasInstallmentPaymentAttribute()
    {
        return $this->installmentPayment()->exists();
    }

    /**
     * Get installment payment details.
     */
    public function getInstallmentDetailsAttribute()
    {
        if (!$this->has_installment_payment) {
            return null;
        }
        
        $installmentPayment = $this->installmentPayment;
        $installmentPlan = $installmentPayment->installmentPlan ?? null;
        
        if (!$installmentPlan) {
            return null;
        }
        
        return [
            'installment_number' => $installmentPayment->installment_number,
            'total_installments' => $installmentPlan->installment_count,
            'installment_amount' => $installmentPayment->due_amount,
            'paid_amount' => $installmentPayment->paid_amount,
            'due_date' => $installmentPayment->due_date?->format('Y-m-d'),
            'paid_at' => $installmentPayment->paid_at?->format('Y-m-d H:i:s'),
            'status' => $installmentPayment->status,
            'plan_status' => $installmentPlan->status,
            'frequency' => $installmentPlan->frequency,
            'next_due_date' => $installmentPlan->getNextDueDate()
        ];
    }

    /**
     * Update payment status based on payments.
     */
    public function updatePaymentStatus()
    {
        $totalAmount = $this->total_amount;
        $amountPaid = $this->payments()->whereIn('status', ['completed', 'paid'])->sum('amount');
        
        if ($amountPaid >= $totalAmount) {
            $this->payment_status = 'paid';
        } elseif ($amountPaid > 0) {
            $this->payment_status = 'partially_paid';
        } else {
            $this->payment_status = 'pending';
        }
        
        // If paid, update invoice status
        if ($this->payment_status === 'paid') {
            $this->status = 'paid';
        }
        
        $this->save();
        
        return $this;
    }

    /**
     * Add a payment to this invoice.
     */
    public function addPayment($amount, $method, $notes = null, $reference = null)
    {
        $payment = Payment::create([
            'hotel_id' => $this->hotel_id, // ADDED
            'invoice_id' => $this->id,
            'amount' => $amount,
            'payment_method' => $method,
            'notes' => $notes,
            'reference' => $reference,
            'status' => 'completed',
            'paid_at' => now(),
        ]);
        
        // Update payment status
        $this->updatePaymentStatus();
        
        // If this is linked to an installment payment, update it
        if ($this->installmentPayment) {
            $this->installmentPayment->update([
                'paid_amount' => $amount,
                'status' => 'paid',
                'paid_at' => now(),
                'payment_method' => $method,
                'notes' => $notes,
            ]);
        }
        
        return $payment;
    }

    /**
     * Add item to invoice.
     */
    public function addItem($description, $quantity, $unitPrice, $taxRate = 0)
    {
        $subtotal = $quantity * $unitPrice;
        $taxAmount = $subtotal * ($taxRate / 100);
        $total = $subtotal + $taxAmount;
        
        $item = InvoiceItem::create([
            'invoice_id' => $this->id,
            'description' => $description,
            'quantity' => $quantity,
            'unit_price' => $unitPrice,
            'subtotal' => $subtotal,
            'tax_rate' => $taxRate,
            'tax_amount' => $taxAmount,
            'total' => $total,
        ]);
        
        // Recalculate invoice totals
        $this->recalculateTotals();
        
        return $item;
    }

    /**
     * Recalculate invoice totals from items.
     */
    public function recalculateTotals()
    {
        $items = $this->items;
        
        $subtotal = $items->sum('subtotal');
        $taxAmount = $items->sum('tax_amount');
        $totalAmount = $items->sum('total');
        
        $this->update([
            'subtotal' => $subtotal,
            'tax_amount' => $taxAmount,
            'total_amount' => $totalAmount,
        ]);
        
        // Update payment status if needed
        if ($totalAmount != $this->total_amount) {
            $this->updatePaymentStatus();
        }
    }

    /**
     * Apply discount to invoice.
     */
    public function applyDiscount($discountAmount, $type = 'fixed')
    {
        if ($type === 'percentage') {
            $discountAmount = ($this->total_amount * $discountAmount) / 100;
        }
        
        $this->discount_amount = $discountAmount;
        $this->total_amount = max(0, $this->total_amount - $discountAmount);
        $this->save();
        
        return $this;
    }

    /**
     * Mark invoice as sent.
     */
    public function markAsSent()
    {
        $this->update([
            'status' => 'sent',
            'date' => now(),
            'due_date' => $this->due_date ?? now()->addDays(config('app.invoice_due_days', 30)),
        ]);
        
        return $this;
    }

    /**
     * Mark invoice as paid.
     */
    public function markAsPaid($method = null, $notes = null)
    {
        if ($this->balance > 0) {
            $this->addPayment($this->balance, $method ?? 'manual', $notes);
        }
        
        $this->update([
            'status' => 'paid',
            'payment_status' => 'paid',
        ]);
        
        return $this;
    }

    /**
     * Get currency symbol.
     */
    private function getCurrencySymbol($currency)
    {
        $symbols = [
            'USD' => '$',
            'EUR' => '€',
            'GBP' => '£',
            'KES' => 'KSh ',
            'NGN' => '₦',
            'GHS' => 'GH₵',
            'ZAR' => 'R',
            'UGX' => 'USh ',
            'TZS' => 'TSh ',
        ];
        
        return $symbols[$currency] ?? $currency . ' ';
    }

    /**
     * Generate invoice number.
     */
    public static function generateInvoiceNumber($type = 'sales', $hotelId = null)
    {
        $prefix = match($type) {
            'sales' => 'INV',
            'purchase' => 'PINV',
            'service' => 'SINV',
            'room_charge' => 'RINV',
            'restaurant' => 'FINV',
            default => 'INV'
        };
        
        $year = date('Y');
        $month = date('m');
        
        // Get last invoice number for this month and this hotel
        $query = static::withoutGlobalScopes()->where('invoice_number', 'like', "{$prefix}-{$year}{$month}%");
        
        if ($hotelId) {
            $query->where('hotel_id', $hotelId);
        } else {
            $query->whereNull('hotel_id');
        }
        
        $lastInvoice = $query->orderBy('invoice_number', 'desc')->first();
        
        if ($lastInvoice && preg_match('/-(\d+)$/', $lastInvoice->invoice_number, $matches)) {
            $sequence = intval($matches[1]) + 1;
        } else {
            $sequence = 1;
        }
        
        return "{$prefix}-{$year}{$month}-" . str_pad($sequence, 4, '0', STR_PAD_LEFT);
    }

    /**
     * Create invoice from reservation.
     */
    public static function createFromReservation(Reservation $reservation, $type = 'room_charge')
    {
        $invoice = static::create([
            'hotel_id' => $reservation->hotel_id, // ADDED
            'type' => $type,
            'contact_type' => Guest::class,
            'contact_id' => $reservation->guest_id,
            'reservation_id' => $reservation->id,
            'total_amount' => $reservation->total_amount,
            'subtotal' => $reservation->total_amount,
            'status' => 'draft',
            'payment_status' => 'pending',
            'module_source' => 'front_desk',
            'source_id' => $reservation->id,
            'date' => now(),
            'due_date' => now()->addDays(config('app.invoice_due_days', 30)),
            'currency' => setting('base_currency', setting('currency_symbol', 'UGX')),
            'exchange_rate' => 1,
        ]);
        
        // Generate invoice number scoped to the reservation's tenant
        $invoice->invoice_number = static::generateInvoiceNumber($type, $reservation->hotel_id);
        $invoice->save();
        
        // Add room charge item
        $invoice->addItem(
            "Room Charge - {$reservation->room->room_number} ({$reservation->nights_count} nights)",
            1,
            $reservation->total_amount,
            config('app.tax_rate', 0)
        );
        
        // If reservation has installment plan, link it
        if ($reservation->installment_plan_id) {
            $invoice->update(['installment_plan_id' => $reservation->installment_plan_id]);
        }
        
        return $invoice;
    }

    /**
     * Create journal entry for this invoice.
     */
    private function createJournalEntry()
    {
        // This would integrate with your accounting system
        // For example:
        if (class_exists(JournalEntry::class)) {
            JournalEntry::create([
                'hotel_id' => $this->hotel_id, // ADDED
                'reference_type' => get_class($this),
                'reference_id' => $this->id,
                'description' => "Invoice {$this->invoice_number} - {$this->contact_name}",
                'amount' => $this->total_amount,
                'type' => $this->type === 'sales' ? 'credit' : 'debit',
                'date' => now(),
            ]);
        }
    }
}