<?php

namespace App\Models;

use DateTimeInterface;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;

class Security extends Model
{
    use HasFactory;
    use SoftDeletes;

    public const float MAX_STORABLE_CHANGE_PERCENT = 999999.9999;

    protected $fillable = [
        'name',
        'trading_name',
        'logo',
        'currency_code',
        'initial_listing_amount',
        'current_amount',
        'min_shares',
        'max_shares_purchase',
        'max_shares_holding',
        'description',
        'is_active',
    ];

    protected function casts(): array
    {
        return [
            'current_amount' => 'decimal:4',
            'is_active' => 'boolean',
        ];
    }

    protected static function booted(): void
    {
        static::creating(function (Security $security): void {
            $security->name = $security->name ?: $security->trading_name;
            $security->currency_code = $security->currency_code ?: 'USD';
            $security->initial_listing_amount = $security->initial_listing_amount ?: 0.0001;
            $security->current_amount = $security->current_amount ?: 0.0001;
            $security->min_shares = $security->min_shares ?: 1;
            $security->max_shares_purchase = $security->max_shares_purchase ?? 0;
            $security->max_shares_holding = $security->max_shares_holding ?? 0;
        });

        static::updating(function (Security $security): void {
            if ($security->isDirty('trading_name')) {
                $security->name = $security->trading_name;
            }
        });

        static::created(function (Security $security): void {
            $security->recordPriceLog(
                (float) $security->current_amount,
                0.0,
                'manual',
                $security->created_at
            );
        });

        static::updated(function (Security $security): void {
            if (! $security->wasChanged('current_amount')) {
                return;
            }

            $previous = (float) $security->getOriginal('current_amount');
            $current = (float) $security->current_amount;
            $changePercent = self::calculateChangePercent($previous, $current);

            $security->recordPriceLog(
                $current,
                $changePercent,
                'system',
                $security->updated_at
            );
        });
    }

    public function orders(): HasMany
    {
        return $this->hasMany(SecurityOrder::class);
    }

    public function holdings(): HasMany
    {
        return $this->hasMany(PortfolioHolding::class);
    }

    public function watchlists(): HasMany
    {
        return $this->hasMany(SecurityWatchlist::class);
    }

    public function logs(): HasMany
    {
        return $this->hasMany(SecurityLog::class);
    }

    public function latestLog(): HasOne
    {
        return $this->hasOne(SecurityLog::class)->latestOfMany('logged_at');
    }

    public function latestChangePercent(): float
    {
        $latestChange = $this->relationLoaded('latestLog')
            ? $this->latestLog?->change_percent
            : $this->latestLog()->value('change_percent');

        return (float) ($latestChange ?? 0);
    }

    public function latestPrice(): float
    {
        $latestPrice = $this->relationLoaded('latestLog')
            ? $this->latestLog?->price
            : $this->latestLog()->value('price');

        return (float) ($latestPrice ?? $this->current_amount ?? 0);
    }

    public function recordPriceLog(float $price, float $changePercent = 0.0, string $source = 'manual', ?DateTimeInterface $loggedAt = null): SecurityLog
    {
        return $this->logs()->create([
            'price' => $price,
            'change_percent' => self::normalizeChangePercent($changePercent),
            'source' => $source,
            'logged_at' => $loggedAt ?? now(),
        ]);
    }

    public static function calculateChangePercent(float $previousPrice, float $currentPrice): float
    {
        if ($previousPrice <= 0.0001) {
            return 0.0;
        }

        $percent = (($currentPrice - $previousPrice) / $previousPrice) * 100;

        return self::normalizeChangePercent($percent);
    }

    private static function normalizeChangePercent(float $value): float
    {
        return max(-self::MAX_STORABLE_CHANGE_PERCENT, min(self::MAX_STORABLE_CHANGE_PERCENT, round($value, 4)));
    }
}
