<?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\PaymentType;

use BookiFlex\Core\Exception\MoneyCalculationException;
use BookiFlex\Core\Model\Identifiable;
use BookiFlex\Core\Util\DateTimeUtils;
use Brick\Math\Exception\MathException;
use Brick\Math\RoundingMode;
use Brick\Money\Money;
use DateTimeImmutable;
use DateInterval;
use DateTimeInterface;

defined( 'ABSPATH' ) || exit;

class PaymentType
{
    use Identifiable;

    /**
     * @param string $name
     * @param string $type
     * @param string $description
     * @param string $prepaymentType
     * @param int $prepaymentValue
     * @param string $ABAPeriod Period (Available before arrival) (hours, days)
     * @param string $ABACondition Condition (Available before arrival) (equal, lte, gte)
     * @param int $ABAValue Value (Available before arrival)
     * @param int $priority
     * @param bool $paymentAuthorization
     * @param bool $active
     * @param string|null $gatewayName
     * @param int|null $timeForConfirmation Time for confirmation in hours
     */
    public function __construct(
        private string $name,
        private string $type,
        private string $description = '',
        private string $prepaymentType = PaymentTypeInterface::DEFAULT_PREPAYMENT_TYPE,
        private int $prepaymentValue = PaymentTypeInterface::DEFAULT_PREPAYMENT_VALUE,
        private string $ABAPeriod = PaymentTypeInterface::DEFAULT_ABA_PERIOD,
        private string $ABACondition = PaymentTypeInterface::DEFAULT_ABA_CONDITION,
        private int $ABAValue = PaymentTypeInterface::DEFAULT_ABA_VALUE,
        private int $priority = 0,
        private bool $paymentAuthorization = false,
        private bool $active = true,
        private ?string $gatewayName = null,
        private ?int $timeForConfirmation = null,
    ) {
    }

    public function isActive(): bool
    {
        return $this->active;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    public function getType(): string
    {
        return $this->type;
    }

    public function getPrepaymentType(): string
    {
        return $this->isPayable() ? $this->prepaymentType : PaymentTypeInterface::DEFAULT_PREPAYMENT_TYPE;
    }

    public function getPrepaymentValue(): int
    {
        return $this->isPayable() ? $this->prepaymentValue : 0;
    }

    public function getABACondition(): string
    {
        return $this->ABACondition;
    }

    public function getABAValue(): int
    {
        return $this->ABAValue;
    }

    public function getABAPeriod(): string
    {
        return $this->ABAPeriod;
    }

    public function getPriority(): int
    {
        return $this->priority;
    }

    /**
     * Computed property: determines if payment record is required based on type
     */
    public function isPayable(): bool
    {
        return in_array($this->type, PaymentTypeInterface::PAYABLE_TYPES, true);
    }

    public function usePaymentAuthorization(): bool
    {
        return $this->isInternetPayment() && $this->paymentAuthorization;
    }

    public function getTimeForConfirmation(): ?int
    {
        return $this->isPayable() ? $this->timeForConfirmation : null;
    }

    /**
     * Return maximum date and time when confirmation by customer is possible (at UTC)
     *
     * @throws \Exception
     */
    public function getDateTimeForConfirmation(): ?DateTimeImmutable
    {
        $timeout = $this->getTimeForConfirmation();

        if ($timeout === null) {
            return null;
        }

        return DateTimeUtils::nowAtLocalTimeZone()
            ->add(new DateInterval('PT' . $timeout . 'H'));
    }

    /**
     * @param Money $totalAmount
     * @param int $lengthOfStay
     * @return Money
     * @throws MoneyCalculationException
     */
    public function calculatePrepayment(Money $totalAmount, int $lengthOfStay): Money
    {
        if ($totalAmount->isZero() || !$this->isPayable()) {
            return Money::zero($totalAmount->getCurrency());
        }

        try {
            if ($this->prepaymentType === PaymentTypeInterface::AMOUNT_PREPAYMENT_TYPE) {
                $decimalPercent = $this->prepaymentValue / 100;
                return $totalAmount->multipliedBy($decimalPercent, RoundingMode::UP);
            }

            return $totalAmount->dividedBy($lengthOfStay, RoundingMode::UP);
        } catch (MathException $e) {
            throw new MoneyCalculationException(
                sprintf(
                    'Failed to calculate money: %s',
                    $e->getMessage()
                ),
                0,
                $e,
                ['error' => $e->getMessage()]
            );
        }
    }

    /**
     * Return true if payment type is available for the given date
     *
     * @param DateTimeInterface $checkInDateTime max possible check-in date and time
     * @return bool
     * @throws \Exception
     */
    public function isAvailableOn(DateTimeInterface $checkInDateTime): bool
    {
        if (!$this->active) {
            return false;
        }

	    $now = new DateTimeImmutable('now', $checkInDateTime->getTimezone());

	    if ($this->ABAPeriod === PaymentTypeInterface::ABA_DAYS_PERIOD) {
		    $todayDate   = $now->setTime(0, 0);
		    $checkinDate = $checkInDateTime->setTime(0, 0);

		    $diff = (int) $todayDate
			    ->diff($checkinDate)
			    ->format('%r%a');
	    } else {
		    $diffInSeconds = $checkInDateTime->getTimestamp() - $now->getTimestamp();
		    $diff = intdiv($diffInSeconds, 3600);
	    }

	    return match ($this->ABACondition) {
		    PaymentTypeInterface::ABA_EQUAL_TYPE => $diff === $this->ABAValue,
		    PaymentTypeInterface::ABA_GTE_TYPE   => $diff >= $this->ABAValue,
		    PaymentTypeInterface::ABA_LTE_TYPE   => $diff <= $this->ABAValue,
		    default => false,
	    };
    }

    public function getGatewayName(): ?string
    {
        return $this->isPayable() ? $this->gatewayName : null;
    }

    public function isInternetPayment(): bool
    {
        return $this->type === PaymentTypeInterface::INTERNET_PAYMENT_TYPE;
    }

    public function isOnArrivalPayment(): bool
    {
        return $this->type === PaymentTypeInterface::PAYMENT_ON_ARRIVAL_TYPE;
    }
}
