<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;

class Account extends Model
{
    use HasFactory, \App\Traits\HotelScopeTrait;

    public $isTenantShared = true;

    protected $table = 'accounts';

    protected $fillable = [
        'hotel_id',
        'code',
        'name',
        'l1_statement',
        'l2_department',
        'l3_group',
        'l4_sub_group',
        'account_type',
        'normal_balance',
        'category',
        'pl_line',
        'report_section',
        'parent_id',
        'is_active',
        'notes'
    ];

    protected $casts = [
        'is_active' => 'boolean',
        'created_at' => 'datetime',
        'updated_at' => 'datetime'
    ];

    protected $appends = [
        'full_path',
        'full_code',
        'balance',
        'is_header',
        'is_posting'
    ];

    // ==================== Relationships ====================

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

    /**
     * Get the parent account
     */
    public function parent(): BelongsTo
    {
        return $this->belongsTo(Account::class, 'parent_id');
    }

    /**
     * Get the child accounts - LIMITED to prevent recursion
     */
    public function children(): HasMany
    {
        return $this->hasMany(Account::class, 'parent_id')->limit(100);
    }

    /**
     * Get all journal entry items for this account
     */
    public function journalEntryItems(): HasMany
    {
        return $this->hasMany(JournalEntryItem::class, 'account_id');
    }

    /**
     * Get all journal transactions for this account
     */
    public function journalTransactions(): HasMany
    {
        return $this->hasMany(JournalTransaction::class, 'account_id');
    }

    /**
     * Get all tags for this account
     */
    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class, 'account_tag', 'account_id', 'tag_id');
    }

    /**
     * Get all budgets for this account
     */
    public function budgets(): HasMany
    {
        return $this->hasMany(Budget::class, 'account_id');
    }

    /**
     * Get all account balances for this account
     */
    public function accountBalances(): HasMany
    {
        return $this->hasMany(AccountBalance::class, 'account_id');
    }

    // ==================== Scopes ====================

    /**
     * Scope to filter by hotel including global accounts
     */
    public function scopeForHotel($query, $hotelId)
    {
        return $query->where(function($q) use ($hotelId) {
            $q->where('hotel_id', $hotelId)
              ->orWhereNull('hotel_id'); // Global accounts have NULL hotel_id
        });
    }

    /**
     * Scope to get only global accounts
     */
    public function scopeGlobal($query)
    {
        return $query->whereNull('hotel_id');
    }

    /**
     * Scope to get only hotel-specific accounts
     */
    public function scopeHotelSpecific($query, $hotelId)
    {
        return $query->where('hotel_id', $hotelId);
    }

    /**
     * Scope to get only active accounts
     */
    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }

    /**
     * Scope to get only posting accounts (type 'P')
     */
    public function scopePostingAccounts($query)
    {
        return $query->where('account_type', 'P');
    }

    /**
     * Scope to get only header accounts (type 'H')
     */
    public function scopeHeaderAccounts($query)
    {
        return $query->where('account_type', 'H');
    }

    /**
     * Scope to get accounts by P&L line
     */
    public function scopeByPlLine($query, $plLine)
    {
        return $query->where('pl_line', $plLine);
    }

    /**
     * Scope to get accounts by report section
     */
    public function scopeByReportSection($query, $section)
    {
        return $query->where('report_section', $section);
    }

    /**
     * Scope to get accounts by category
     */
    public function scopeByCategory($query, $category)
    {
        return $query->where('category', $category);
    }

    /**
     * Scope to get root accounts (no parent)
     */
    public function scopeRootAccounts($query)
    {
        return $query->whereNull('parent_id');
    }

    // ==================== Accessors ====================

    /**
     * Get the full account path (including all ancestors) - SAFE version with limit
     */
    public function getFullPathAttribute(): string
    {
        $path = [];
        $account = $this;
        $maxDepth = 10; // Prevent infinite loops
        $depth = 0;
        
        while ($account && $depth < $maxDepth) {
            array_unshift($path, $account->code . ' - ' . $account->name);
            $account = $account->parent;
            $depth++;
        }
        
        return implode(' > ', $path);
    }

    /**
     * Get the full account code with parent codes - SAFE version with limit
     */
    public function getFullCodeAttribute(): string
    {
        $codes = [];
        $account = $this;
        $maxDepth = 10; // Prevent infinite loops
        $depth = 0;
        
        while ($account && $depth < $maxDepth) {
            array_unshift($codes, $account->code);
            $account = $account->parent;
            $depth++;
        }
        
        return implode('.', $codes);
    }

    /**
     * Check if account is a header account
     */
    public function getIsHeaderAttribute(): bool
    {
        return $this->account_type === 'H';
    }

    /**
     * Check if account is a posting account
     */
    public function getIsPostingAttribute(): bool
    {
        return $this->account_type === 'P';
    }

    /**
     * Check if account is global
     */
    public function getIsGlobalAttribute(): bool
    {
        return is_null($this->hotel_id);
    }

    /**
     * Get the current balance of the account
     */
    public function getBalanceAttribute(): float
    {
        // For performance, we can use a direct sum if we don't trust AccountBalance table
        // or just return from the AccountBalance table which we'll refresh.
        // Let's use the AccountBalance table for the dashboard.
        $latestBalance = $this->accountBalances()
            ->orderBy('period_date', 'desc')
            ->first();

        return $latestBalance ? (float) $latestBalance->balance : 0.00;
    }

    /**
     * Get the normal balance side in words
     */
    public function getNormalBalanceSideAttribute(): string
    {
        return $this->normal_balance === 'Dr' ? 'Debit' : 'Credit';
    }

    // ==================== REMOVED RECURSIVE METHODS ====================
    // The following methods were removed to prevent infinite recursion:
    // - descendants()
    // - ancestors()

    /**
     * Check if account has children
     */
    public function hasChildren(): bool
    {
        return $this->children()->exists();
    }

    /**
     * Check if account has transactions
     */
    public function hasTransactions(): bool
    {
        return $this->journalTransactions()->exists() || $this->journalEntryItems()->exists();
    }

    /**
     * Get the balance for a specific period
     */
    public function getBalanceForPeriod($periodDate, $period = 'monthly'): ?AccountBalance
    {
        return $this->accountBalances()
            ->where('period_date', $periodDate)
            ->where('period', $period)
            ->first();
    }

    /**
     * Incrementally update the account balance using a single item
     */
    public function updateBalanceFromItem(JournalEntryItem $item): void
    {
        $date = $item->journalEntry->date ?? now();
        $periodDate = (\Carbon\Carbon::parse($date))->copy()->startOfMonth()->toDateString();
        // Use fiscal_year from JournalEntry if available, else calendar year
        $fiscalYear = $item->journalEntry->fiscal_year_id ?? (\Carbon\Carbon::parse($date))->year;
        
        $balanceRecord = AccountBalance::firstOrCreate(
            [
                'hotel_id' => $this->hotel_id,
                'account_id' => $this->id,
                'period_date' => $periodDate,
                'period' => 'monthly',
                'fiscal_year' => $fiscalYear
            ],
            [
                'balance' => 0,
                'debit_sum' => 0,
                'credit_sum' => 0
            ]
        );
        
        $debit = (float) ($item->debit ?? 0);
        $credit = (float) ($item->credit ?? 0);
        
        $balanceRecord->debit_sum += $debit;
        $balanceRecord->credit_sum += $credit;
        
        $balanceRecord->balance = $this->normal_balance === 'Dr'
            ? $balanceRecord->debit_sum - $balanceRecord->credit_sum
            : $balanceRecord->credit_sum - $balanceRecord->debit_sum;
            
        $balanceRecord->save();
    }

    /**
     * Update the account balance (called after posting)
     */
    public function refreshBalance($periodDate, $fiscalYear): AccountBalance
    {
        $debitSum = $this->journalEntryItems()
            ->whereHas('journalEntry', function($query) use ($periodDate) {
                $query->where('status', 'posted')
                    ->whereMonth('date', $periodDate->month)
                    ->whereYear('date', $periodDate->year);
            })->sum('debit');

        $creditSum = $this->journalEntryItems()
            ->whereHas('journalEntry', function($query) use ($periodDate) {
                $query->where('status', 'posted')
                    ->whereMonth('date', $periodDate->month)
                    ->whereYear('date', $periodDate->year);
            })->sum('credit');

        $balance = $this->normal_balance === 'Dr' 
            ? $debitSum - $creditSum 
            : $creditSum - $debitSum;

        $accountBalance = AccountBalance::updateOrCreate(
            [
                'hotel_id' => $this->hotel_id,
                'account_id' => $this->id,
                'period_date' => $periodDate->startOfMonth()->toDateString(),
                'period' => 'monthly',
                'fiscal_year' => $fiscalYear
            ],
            [
                'balance' => $balance,
                'debit_sum' => $debitSum,
                'credit_sum' => $creditSum
            ]
        );

        return $accountBalance;
    }

    /**
     * Boot method for model events
     */
    protected static function boot()
    {
        parent::boot();

        static::saving(function ($account) {
            // Ensure code is unique within the same scope (global or hotel-specific)
            $query = static::where('code', $account->code)
                ->where('id', '!=', $account->id);
            
            if (is_null($account->hotel_id)) {
                // For global accounts, check only among other global accounts
                $query->whereNull('hotel_id');
            } else {
                // For hotel-specific accounts, check within the same hotel
                $query->where('hotel_id', $account->hotel_id);
            }
            
            if ($query->exists()) {
                throw new \Exception('Account code must be unique within its scope (global or hotel).');
            }
        });

        static::deleting(function ($account) {
            // Prevent deletion if account has children
            if ($account->hasChildren()) {
                throw new \Exception('Cannot delete account that has sub-accounts.');
            }

            // Prevent deletion if account has transactions
            if ($account->hasTransactions()) {
                throw new \Exception('Cannot delete account with existing transactions.');
            }
        });
    }
}