<?php
/**
 * BookiFlex Core
 *
 * @package    BookiFlex
 * @subpackage Core
 * @author     BookiFlex
 * @copyright  Copyright (c) 2025 BookiFlex
 * @license    GPL-2.0-or-later
 *
 * @link       https://bookiflex.com
 */

declare(strict_types=1);

namespace BookiFlex\Core\Model\Penalty;

use BookiFlex\Core\Exception\RuntimeException;
use BookiFlex\Core\Model\Identifiable;
use BookiFlex\Core\Model\Reservation\Reservation;
use BookiFlex\Core\Model\Timestamps;
use BookiFlex\Core\Util\DateTimeUtils;
use Brick\Money\Money;
use DateTimeImmutable;

defined( 'ABSPATH' ) || exit;

/**
 * Represents a cancellation penalty applied to a reservation.
 *
 * Penalties are financial charges applied when a reservation is cancelled
 * according to the cancellation policy rules.
 *
 * @package BookiFlex\Core\Model\Penalty
 */
class Penalty
{
    use Timestamps;
    use Identifiable;

    /** @var Reservation|null Associated reservation */
    private ?Reservation $reservation = null;

    /** @var string|null optional manager note */
    private ?string $comment = null;

    /**
     * Create a new penalty instance.
     *
     * @param Money $amount Penalty amount
     * @param DateTimeImmutable $appliedAt When penalty was calculated/applied
     * @param string $description Human-readable description
     * @param PenaltyStatus $status Current penalty status
     */
    public function __construct(
        private readonly Money $amount,
        private readonly DateTimeImmutable $appliedAt,
        private readonly string $description,
        private readonly PenaltyStatus $status
    ) {
        $this->createdAt = DateTimeUtils::nowUtc();
    }

    // ========================================================================
    // Properties Access
    // ========================================================================

    /**
     * Get penalty amount.
     */
    public function getAmount(): Money
    {
        return $this->amount;
    }

    /**
     * Get penalty description.
     */
    public function getDescription(): string
    {
        return $this->description;
    }

    /**
     * Get when penalty was applied.
     */
    public function getAppliedAt(): DateTimeImmutable
    {
        return $this->appliedAt;
    }

    /**
     * Get current status.
     */
    public function getStatus(): PenaltyStatus
    {
        return $this->status;
    }

    /**
     * Get associated reservation.
     */
    public function getReservation(): ?Reservation
    {
        return $this->reservation;
    }

    /**
     * Set associated reservation.
     */
    public function setReservation(?Reservation $reservation): self
    {
        $this->reservation = $reservation;
        return $this;
    }

    /**
     * Get payment reference.
     */
    public function getComment(): ?string
    {
        return $this->comment;
    }

    // ========================================================================
    // Status Management
    // ========================================================================

    /**
     * Check if penalty is pending.
     */
    public function isCalculated(): bool
    {
        return $this->status->isEquals(PenaltyStatus::CALCULATED);
    }

    /**
     * Check if penalty has been resolved.
     */
    public function isResolved(): bool
    {
        return $this->status->isEquals(PenaltyStatus::RESOLVED);
    }

    /**
     * Mark penalty as refunded.
     *
     * @param string|null $comment Optional manager note
     * @return bool True if status was changed
     * @throws RuntimeException
     */
    public function markAsResolved(?string $comment = null): bool
    {
        if ($this->status->canTransitionTo(PenaltyStatus::RESOLVED)) {
            $this->status->transitionTo(PenaltyStatus::RESOLVED);
            $this->comment = $comment;
            $this->updatedAt = DateTimeUtils::nowUtc();
            return true;
        }
        return false;
    }

    // ========================================================================
    // Formatting and Display
    // ========================================================================

    /**
     * Get formatted amount with currency symbol.
     *
     * @return string Formatted amount like "$100.00" or "€50.00"
     */
    public function getFormattedAmount(): string
    {
        $currency = $this->amount->getCurrency()->getCurrencyCode();
        $amount = $this->amount->getAmount()->toFloat();

        // Simple currency symbol mapping (can be extended)
        $symbols = [
            'USD' => '$',
            'EUR' => '€',
            'GBP' => '£',
            'JPY' => '¥',
        ];

        $symbol = $symbols[$currency] ?? $currency . ' ';

        if ($currency === 'JPY') {
            // Japanese Yen doesn't use decimal places
            return $symbol . number_format($amount, 0);
        }

        return $symbol . number_format($amount, 2);
    }

    /**
     * Get summary for display.
     *
     * @return string Summary like "Cancellation penalty: $100.00 (PAID)"
     */
    public function getSummary(): string
    {
        $summary = $this->description . ': ' . $this->getFormattedAmount();

        if (!$this->isCalculated()) {
            $summary .= ' (' . strtoupper($this->status->__toString()) . ')';
        }

        return $summary;
    }

    // ========================================================================
    // Serialization
    // ========================================================================

    /**
     * Convert penalty to array representation.
     *
     * @return array
     */
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'amount' => $this->amount->getAmount()->toFloat(),
            'currency' => $this->amount->getCurrency()->getCurrencyCode(),
            'formatted_amount' => $this->getFormattedAmount(),
            'description' => $this->description,
            'status' => (string) $this->status,
            'applied_at' => $this->appliedAt->format('Y-m-d H:i:s'),
            'comment' => $this->comment,
            'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
            'updated_at' => $this->updatedAt?->format('Y-m-d H:i:s'),
        ];
    }
}
