<?php

declare(strict_types=1);

namespace App\Models;

use App\ValueObjects\Money;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
 * Company Financial Settings Model.
 *
 * Stores financial configuration and opening balances for each company.
 * This enables proper cash box reconciliation, capital tracking, and
 * financial policy enforcement.
 *
 * KEY CONCEPTS:
 * - Opening Balance: Initial cash when company starts using the system
 * - Initial Capital: Working capital available for lending operations
 * - Auto-lock: Automatically lock old financial records after X days
 *
 * IMPORTANT:
 * - One record per company (1:1 relationship)
 * - Opening balance can only be set once and locked
 * - Settings affect all financial transactions for the company
 *
 * @property int $id
 * @property int $company_id
 * @property string $opening_balance
 * @property Carbon|null $opening_balance_date
 * @property string $initial_capital
 * @property bool $opening_balance_locked
 * @property bool $profit_withdrawal_requires_approval
 * @property bool $capital_transaction_requires_approval
 * @property string $approval_threshold_amount
 * @property int $capital_transaction_lock_hours
 * @property int $auto_lock_after_days
 * @property int $fiscal_year_start_month
 * @property int $fiscal_year_start_day
 * @property string $currency_code
 * @property int $decimal_places
 * @property bool $track_principal_interest_separately
 * @property int|null $created_by_user_id
 * @property int|null $updated_by_user_id
 * @property Carbon $created_at
 * @property Carbon $updated_at
 * @property-read Company $company
 * @property-read User|null $createdBy
 * @property-read User|null $updatedBy
 */
class CompanyFinancialSettings extends Model
{
    use HasFactory;

    protected $table = 'company_financial_settings';

    protected $fillable = [
        'company_id',
        'opening_balance',
        'opening_balance_date',
        'initial_capital',
        'opening_balance_locked',
        'profit_withdrawal_requires_approval',
        'capital_transaction_requires_approval',
        'approval_threshold_amount',
        'capital_transaction_lock_hours',
        'auto_lock_after_days',
        'fiscal_year_start_month',
        'fiscal_year_start_day',
        'currency_code',
        'decimal_places',
        'track_principal_interest_separately',
        'created_by_user_id',
        'updated_by_user_id',
        // Credit constraints
        'min_credit_amount',
        'max_credit_amount',
        'default_credit_amount',
        'min_interest_rate',
        'max_interest_rate',
        'default_interest_rate',
        'min_installments',
        'max_installments',
        'default_installments',
        // Periodicity
        'allowed_periodicities',
        'default_periodicity',
        'default_grace_days',
        // Late payment
        'late_fee_percentage',
        'late_fee_fixed',
        'days_until_delayed',
        'days_until_default',
        // Payment behavior
        'allow_partial_payments',
        'allow_overpayments',
        'auto_close_on_full_payment',
        'apply_payment_to_oldest_first',
        // Simplified input
        'simplified_input_enabled',
        'simplified_input_multiplier',
        'simplified_input_contexts',
        'simplified_input_require_confirm',
        // UI preferences
        'show_client_balance_on_payment',
        'require_payment_notes',
        'show_installment_breakdown',
    ];

    protected $casts = [
        'opening_balance' => 'decimal:2',
        'opening_balance_date' => 'date',
        'initial_capital' => 'decimal:2',
        'opening_balance_locked' => 'boolean',
        'profit_withdrawal_requires_approval' => 'boolean',
        'capital_transaction_requires_approval' => 'boolean',
        'approval_threshold_amount' => 'decimal:2',
        'capital_transaction_lock_hours' => 'integer',
        'auto_lock_after_days' => 'integer',
        'fiscal_year_start_month' => 'integer',
        'fiscal_year_start_day' => 'integer',
        'decimal_places' => 'integer',
        'track_principal_interest_separately' => 'boolean',
        // Credit constraints
        'min_credit_amount' => 'decimal:2',
        'max_credit_amount' => 'decimal:2',
        'default_credit_amount' => 'decimal:2',
        'min_interest_rate' => 'decimal:2',
        'max_interest_rate' => 'decimal:2',
        'default_interest_rate' => 'decimal:2',
        'min_installments' => 'integer',
        'max_installments' => 'integer',
        'default_installments' => 'integer',
        // Periodicity
        'allowed_periodicities' => 'array',
        'default_grace_days' => 'integer',
        // Late payment
        'late_fee_percentage' => 'decimal:2',
        'late_fee_fixed' => 'decimal:2',
        'days_until_delayed' => 'integer',
        'days_until_default' => 'integer',
        // Payment behavior
        'allow_partial_payments' => 'boolean',
        'allow_overpayments' => 'boolean',
        'auto_close_on_full_payment' => 'boolean',
        'apply_payment_to_oldest_first' => 'boolean',
        // Simplified input
        'simplified_input_enabled' => 'boolean',
        'simplified_input_multiplier' => 'integer',
        'simplified_input_contexts' => 'array',
        'simplified_input_require_confirm' => 'boolean',
        // UI preferences
        'show_client_balance_on_payment' => 'boolean',
        'require_payment_notes' => 'boolean',
        'show_installment_breakdown' => 'boolean',
    ];

    protected $attributes = [
        'opening_balance' => 0.00,
        'initial_capital' => 0.00,
        'opening_balance_locked' => false,
        'profit_withdrawal_requires_approval' => true,
        'capital_transaction_requires_approval' => true,
        'approval_threshold_amount' => 5000000.00,
        'capital_transaction_lock_hours' => 24,
        'auto_lock_after_days' => 30,
        'fiscal_year_start_month' => 1,
        'fiscal_year_start_day' => 1,
        'currency_code' => 'COP',
        'decimal_places' => 2,
        'track_principal_interest_separately' => true,
        // Credit constraints defaults
        'min_credit_amount' => 100000.00,
        'max_credit_amount' => 50000000.00,
        'min_interest_rate' => 0.00,
        'max_interest_rate' => 30.00,
        'min_installments' => 1,
        'max_installments' => 60,
        // Periodicity defaults
        'default_periodicity' => 'monthly',
        'default_grace_days' => 0,
        // Late payment defaults
        'late_fee_percentage' => 0.00,
        'late_fee_fixed' => 0.00,
        'days_until_delayed' => 1,
        'days_until_default' => 90,
        // Payment behavior defaults
        'allow_partial_payments' => true,
        'allow_overpayments' => true,
        'auto_close_on_full_payment' => true,
        'apply_payment_to_oldest_first' => true,
        // Simplified input defaults
        'simplified_input_enabled' => false,
        'simplified_input_multiplier' => 1000,
        'simplified_input_require_confirm' => true,
        // UI defaults
        'show_client_balance_on_payment' => true,
        'require_payment_notes' => false,
        'show_installment_breakdown' => true,
    ];

    // ═══════════════════════════════════════════════════════════════════════
    // RELATIONSHIPS
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * The company these settings belong to.
     */
    public function company(): BelongsTo
    {
        return $this->belongsTo(Company::class);
    }

    /**
     * User who created these settings.
     */
    public function createdBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'created_by_user_id');
    }

    /**
     * User who last updated these settings.
     */
    public function updatedBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'updated_by_user_id');
    }

    // ═══════════════════════════════════════════════════════════════════════
    // ACCESSORS (Money Value Objects)
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * Get opening balance as Money object.
     */
    public function getOpeningBalanceMoney(): Money
    {
        return Money::of($this->opening_balance);
    }

    /**
     * Get initial capital as Money object.
     */
    public function getInitialCapitalMoney(): Money
    {
        return Money::of($this->initial_capital);
    }

    /**
     * Get approval threshold as Money object.
     */
    public function getApprovalThresholdMoney(): Money
    {
        return Money::of($this->approval_threshold_amount);
    }

    // ═══════════════════════════════════════════════════════════════════════
    // BUSINESS LOGIC METHODS
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * Check if opening balance can still be modified.
     */
    public function canModifyOpeningBalance(): bool
    {
        return ! $this->opening_balance_locked;
    }

    /**
     * Lock the opening balance permanently.
     */
    public function lockOpeningBalance(): bool
    {
        if ($this->opening_balance_locked) {
            return false;
        }

        $this->opening_balance_locked = true;

        return $this->save();
    }

    /**
     * Check if an expense amount requires approval.
     */
    public function requiresApprovalForAmount(float|string $amount): bool
    {
        $money = Money::of($amount);

        return $money->greaterThan($this->getApprovalThresholdMoney());
    }

    /**
     * Get the auto-lock cutoff date.
     * Records before this date should be auto-locked.
     */
    public function getAutoLockCutoffDate(): Carbon
    {
        return now()->subDays($this->auto_lock_after_days)->startOfDay();
    }

    /**
     * Get the capital transaction lock cutoff datetime.
     */
    public function getCapitalTransactionLockCutoff(): Carbon
    {
        return now()->subHours($this->capital_transaction_lock_hours);
    }

    /**
     * Check if a record should be auto-locked based on its date.
     */
    public function shouldAutoLock(Carbon $recordDate): bool
    {
        return $recordDate->lt($this->getAutoLockCutoffDate());
    }

    /**
     * Check if a capital transaction should be locked based on creation time.
     */
    public function isCapitalTransactionLocked(Carbon $createdAt): bool
    {
        return $createdAt->lt($this->getCapitalTransactionLockCutoff());
    }

    /**
     * Get the fiscal year start date for a given year.
     */
    public function getFiscalYearStart(int $year): Carbon
    {
        return Carbon::create(
            $year,
            $this->fiscal_year_start_month,
            $this->fiscal_year_start_day
        )->startOfDay();
    }

    /**
     * Get the current fiscal year start date.
     */
    public function getCurrentFiscalYearStart(): Carbon
    {
        $today = now();
        $currentYearStart = $this->getFiscalYearStart($today->year);

        // If we haven't reached this year's fiscal year start, use previous year
        if ($today->lt($currentYearStart)) {
            return $this->getFiscalYearStart($today->year - 1);
        }

        return $currentYearStart;
    }

    // ═══════════════════════════════════════════════════════════════════════
    // CREDIT VALIDATION METHODS
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * Validate a credit amount against company constraints.
     *
     * @return array{valid: bool, errors: array<string>}
     */
    public function validateCreditAmount(float|string $amount): array
    {
        $amount = (float) $amount;
        $errors = [];

        if ($amount < (float) $this->min_credit_amount) {
            $errors[] = 'El monto mínimo es '.number_format((float) $this->min_credit_amount, 0, ',', '.');
        }

        if ($amount > (float) $this->max_credit_amount) {
            $errors[] = 'El monto máximo es '.number_format((float) $this->max_credit_amount, 0, ',', '.');
        }

        return [
            'valid' => empty($errors),
            'errors' => $errors,
        ];
    }

    /**
     * Validate an interest rate against company constraints.
     *
     * @return array{valid: bool, errors: array<string>}
     */
    public function validateInterestRate(float|string $rate): array
    {
        $rate = (float) $rate;
        $errors = [];

        if ($rate < (float) $this->min_interest_rate) {
            $errors[] = "La tasa mínima es {$this->min_interest_rate}%";
        }

        if ($rate > (float) $this->max_interest_rate) {
            $errors[] = "La tasa máxima es {$this->max_interest_rate}%";
        }

        return [
            'valid' => empty($errors),
            'errors' => $errors,
        ];
    }

    /**
     * Validate installment count against company constraints.
     *
     * @return array{valid: bool, errors: array<string>}
     */
    public function validateInstallments(int $count): array
    {
        $errors = [];

        if ($count < $this->min_installments) {
            $errors[] = "El mínimo de cuotas es {$this->min_installments}";
        }

        if ($count > $this->max_installments) {
            $errors[] = "El máximo de cuotas es {$this->max_installments}";
        }

        return [
            'valid' => empty($errors),
            'errors' => $errors,
        ];
    }

    /**
     * Check if a periodicity is allowed.
     */
    public function isPeriodicityAllowed(string $periodicity): bool
    {
        $allowed = $this->allowed_periodicities ?? ['daily', 'weekly', 'biweekly', 'monthly'];

        return in_array($periodicity, $allowed);
    }

    /**
     * Get allowed periodicities as options for select.
     */
    public function getAllowedPeriodicitiesOptions(): array
    {
        $labels = [
            'daily' => 'Diaria',
            'weekly' => 'Semanal',
            'biweekly' => 'Quincenal',
            'monthly' => 'Mensual',
        ];

        $allowed = $this->allowed_periodicities ?? array_keys($labels);

        return collect($allowed)
            ->mapWithKeys(fn ($p) => [$p => $labels[$p] ?? ucfirst($p)])
            ->toArray();
    }

    // ═══════════════════════════════════════════════════════════════════════
    // SIMPLIFIED INPUT METHODS
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * Check if simplified input is enabled.
     *
     * Gated ONLY by the master toggle (simplified_input_enabled). The optional
     * $context parameter is kept for backward signature compatibility but is
     * ignored — simplified input applies uniformly to every context (credit,
     * payment, expense, partner transactions, visit promised amount) once the
     * master toggle is on.
     */
    public function isSimplifiedInputEnabled(?string $context = null): bool
    {
        return (bool) $this->simplified_input_enabled;
    }

    /**
     * Convert simplified input to real amount.
     * E.g., 1500 × 1000 = 1,500,000
     */
    public function convertSimplifiedToReal(float|int $simplifiedValue): float
    {
        return (float) $simplifiedValue * $this->simplified_input_multiplier;
    }

    /**
     * Convert real amount to simplified display.
     * E.g., 1,500,000 ÷ 1000 = 1500
     */
    public function convertRealToSimplified(float|int $realValue): float
    {
        return (float) $realValue / $this->simplified_input_multiplier;
    }

    /**
     * Get the multiplier label for UI display.
     * E.g., "× 1,000" or "× 1,000,000"
     */
    public function getSimplifiedMultiplierLabel(): string
    {
        return '× '.number_format($this->simplified_input_multiplier, 0, ',', '.');
    }

    /**
     * Check if a simplified value looks suspicious (might be already in real form).
     * E.g., if user types 1000000 when they meant 1000
     */
    public function isSimplifiedValueSuspicious(float|int $simplifiedValue): bool
    {
        // If the simplified value is >= 1 million, it might be a mistake
        // (user might have forgotten they're in simplified mode)
        return $simplifiedValue >= 1000000;
    }

    /**
     * Get simplified input metadata for audit logging.
     */
    public function getSimplifiedInputMetadata(float|int $inputValue, string $context): array
    {
        return [
            'input_context' => $context,
            'input_mode' => 'simplified',
            'input_value' => $inputValue,
            'multiplier' => $this->simplified_input_multiplier,
            'final_amount' => $this->convertSimplifiedToReal($inputValue),
        ];
    }

    // ═══════════════════════════════════════════════════════════════════════
    // STATIC HELPERS
    // ═══════════════════════════════════════════════════════════════════════

    /**
     * Get or create settings for a company.
     */
    public static function forCompany(int $companyId): self
    {
        return static::firstOrCreate(
            ['company_id' => $companyId],
            ['created_by_user_id' => auth()->id()]
        );
    }

    /**
     * Get settings for the current user's company.
     */
    public static function forCurrentCompany(): self
    {
        $companyId = auth()->user()?->company_id;

        if (! $companyId) {
            throw new \RuntimeException('No company context available');
        }

        return static::forCompany($companyId);
    }
}
