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

use BookiFlex\Core\Model\Penalty\Penalty;
use BookiFlex\Core\Model\Penalty\PenaltyStatus;
use BookiFlex\Core\Model\Reservation\Reservation;
use Cycle\ORM\Select\Repository;
use DateTimeImmutable;

defined( 'ABSPATH' ) || exit;

/**
 * Repository for Penalty entities.
 * Provides methods for querying and managing penalties.
 */
class PenaltyRepository extends Repository
{
    /**
     * Find penalty by ID.
     *
     * @param int $id
     * @return Penalty|null
     */
    public function findById(int $id): ?Penalty
    {
        return $this->findByPK($id);
    }

    /**
     * Find all penalties for a reservation.
     *
     * @param int|Reservation $reservation Reservation entity or ID
     * @return Penalty[]
     */
    public function findByReservation(Reservation|int $reservation): array
    {
        $reservationId = $reservation instanceof Reservation
            ? $reservation->getId()
            : $reservation;

        return $this->select()
            ->where('reservation_id', $reservationId)
            ->orderBy('applied_at', 'DESC')
            ->fetchAll();
    }

    /**
     * Find penalties by status.
     *
     * @param string $status Status constant from PenaltyStatus
     * @return Penalty[]
     */
    public function findByStatus(string $status): array
    {
        return $this->select()
            ->where('status', $status)
            ->orderBy('created_at', 'DESC')
            ->fetchAll();
    }

    /**
     * Find pending penalties that need to be collected.
     *
     * @return Penalty[]
     */
    public function findCalculatedPenalties(): array
    {
        return $this->findByStatus(PenaltyStatus::CALCULATED);
    }

    /**
     * Find paid penalties.
     *
     * @param DateTimeImmutable|null $fromDate Optional start date filter
     * @param DateTimeImmutable|null $toDate Optional end date filter
     * @return Penalty[]
     */
    public function findResolvedPenalties(
        ?DateTimeImmutable $fromDate = null,
        ?DateTimeImmutable $toDate = null
    ): array {
        $query = $this->select()
            ->where('status', PenaltyStatus::RESOLVED);

        if ($fromDate !== null) {
            $query->where('applied_at', '>=', $fromDate->format('Y-m-d H:i:s'));
        }

        if ($toDate !== null) {
            $query->where('applied_at', '<=', $toDate->format('Y-m-d H:i:s'));
        }

        return $query
            ->orderBy('applied_at', 'DESC')
            ->fetchAll();
    }

    /**
     * Find penalties applied within a date range.
     *
     * @param DateTimeImmutable $startDate
     * @param DateTimeImmutable $endDate
     * @return Penalty[]
     */
    public function findByDateRange(
        DateTimeImmutable $startDate,
        DateTimeImmutable $endDate
    ): array {
        return $this->select()
            ->where('applied_at', '>=', $startDate->format('Y-m-d H:i:s'))
            ->where('applied_at', '<=', $endDate->format('Y-m-d H:i:s'))
            ->orderBy('applied_at', 'DESC')
            ->fetchAll();
    }

    /**
     * Find penalties with payment reference.
     *
     * @param string $paymentReference
     * @return Penalty|null
     */
    public function findByPaymentReference(string $paymentReference): ?Penalty
    {
        return $this->select()
            ->where('payment_reference', $paymentReference)
            ->fetchOne();
    }

    /**
     * Count penalties by status.
     *
     * @param string|null $status Optional status filter
     * @return int
     */
    public function countByStatus(?string $status = null): int
    {
        $query = $this->select();

        if ($status !== null) {
            $query->where('status', $status);
        }

        return $query->count();
    }

    /**
     * Calculate total amount of penalties for a reservation.
     *
     * @param int|Reservation $reservation
     * @param string|null $status Optional status filter
     * @return float Total amount in base currency units
     */
    public function calculateTotalAmount(Reservation|int $reservation, ?string $status = null): float
    {
        $reservationId = $reservation instanceof Reservation
            ? $reservation->getId()
            : $reservation;

        $query = $this->select()
            ->where('reservation_id', $reservationId);

        if ($status !== null) {
            $query->where('status', $status);
        }

        $penalties = $query->fetchAll();

        $total = 0.0;
        foreach ($penalties as $penalty) {
            $total += $penalty->getAmount()->getAmount()->toFloat();
        }

        return $total;
    }

    /**
     * Find penalties that need refund processing.
     *
     * @return Penalty[]
     */
    public function findPenaltiesForRefund(): array
    {
        return $this->select()
            ->where('status', PenaltyStatus::RESOLVED)
            ->where('payment_reference', 'IS NOT', null)
            ->orderBy('updated_at', 'DESC')
            ->fetchAll();
    }

    /**
     * Get penalties statistics for a period.
     *
     * @param DateTimeImmutable $startDate
     * @param DateTimeImmutable $endDate
     * @return array Statistics array with counts and amounts
     */
    public function getStatistics(
        DateTimeImmutable $startDate,
        DateTimeImmutable $endDate
    ): array {
        $penalties = $this->findByDateRange($startDate, $endDate);

        $stats = [
            'total_count' => count($penalties),
            'by_status' => [],
            'total_amount' => 0.0,
            'amount_by_status' => [],
        ];

        foreach ($penalties as $penalty) {
            $status = (string) $penalty->getStatus();
            $amount = $penalty->getAmount()->getAmount()->toFloat();

            // Count by status
            if (!isset($stats['by_status'][$status])) {
                $stats['by_status'][$status] = 0;
                $stats['amount_by_status'][$status] = 0.0;
            }
            $stats['by_status'][$status]++;
            $stats['amount_by_status'][$status] += $amount;

            // Total amount
            $stats['total_amount'] += $amount;
        }

        return $stats;
    }

    /**
     * Find overdue pending penalties.
     * Penalties that are pending for more than specified days.
     *
     * @param int $daysOverdue Number of days to consider overdue
     * @return Penalty[]
     */
    public function findOverduePenalties(int $daysOverdue = 7): array
    {
        $overdueDate = new DateTimeImmutable("-{$daysOverdue} days");

        return $this->select()
            ->where('status', PenaltyStatus::CALCULATED)
            ->where('applied_at', '<=', $overdueDate->format('Y-m-d H:i:s'))
            ->orderBy('applied_at', 'ASC')
            ->fetchAll();
    }

    /**
     * Check if reservation has any penalties.
     *
     * @param int|Reservation $reservation
     * @return bool
     */
    public function hasAnyPenalties(Reservation|int $reservation): bool
    {
        $reservationId = $reservation instanceof Reservation
            ? $reservation->getId()
            : $reservation;

        return $this->select()
                ->where('reservation_id', $reservationId)
                ->count() > 0;
    }

    /**
     * Check if reservation has unpaid penalties.
     *
     * @param int|Reservation $reservation
     * @return bool
     */
    public function hasUnpaidPenalties(Reservation|int $reservation): bool
    {
        $reservationId = $reservation instanceof Reservation
            ? $reservation->getId()
            : $reservation;

        return $this->select()
                ->where('reservation_id', $reservationId)
                ->where('status', PenaltyStatus::CALCULATED)
                ->count() > 0;
    }
}
