<?php

namespace App\Models;

/**
 * A rate plan is a date-range rule applied to one or more room types —
 * no occupancy-based dynamic pricing (explicitly out of scope per the
 * Phase 3 brief). See rate_plan_room_types for the many-to-many link.
 */
class RatePlanModel extends TenantModel
{
    protected $table            = 'rate_plans';
    protected $primaryKey       = 'id';
    protected $useAutoIncrement = true;
    protected $returnType       = 'object';
    protected $useTimestamps    = true;

    protected $allowedFields = [
        'name',
        'description',
        'date_start',
        'date_end',
        'adjustment_type',
        'adjustment_value',
        'is_active',
    ];

    protected $validationRules = [
        'name'             => 'required|max_length[150]',
        'description'      => 'permit_empty|max_length[255]',
        'date_start'       => 'required|valid_date[Y-m-d]',
        'date_end'         => 'required|valid_date[Y-m-d]',
        'adjustment_type'  => 'required|in_list[flat,percentage]',
        'adjustment_value' => 'required|decimal',
    ];

    public function roomTypeIds(int $ratePlanId): array
    {
        // Cast to int — raw query builder results come back as strings
        // (mysqli), and resolveWithPlan() strict-compares against an
        // already-int-coerced room type id.
        return array_map('intval', array_column(
            $this->db->table('rate_plan_room_types')->select('room_type_id')->where('rate_plan_id', $ratePlanId)->get()->getResultArray(),
            'room_type_id'
        ));
    }

    /**
     * Replaces the plan's room-type links wholesale — simpler than diffing
     * for a form that just posts "these are the checked boxes now."
     */
    public function syncRoomTypes(int $ratePlanId, array $roomTypeIds): void
    {
        $this->db->table('rate_plan_room_types')->where('rate_plan_id', $ratePlanId)->delete();

        $rows = array_map(static fn ($roomTypeId) => ['rate_plan_id' => $ratePlanId, 'room_type_id' => $roomTypeId], $roomTypeIds);

        if ($rows !== []) {
            $this->db->table('rate_plan_room_types')->insertBatch($rows);
        }
    }

    /**
     * @return array Each plan also gets room_type_names/room_type_ids
     *                (the latter used by RatePlanController::index()'s
     *                room-type filter) attached.
     */
    public function withRoomTypeNames(): array
    {
        $plans = $this->orderBy('date_start', 'desc')->findAll();

        foreach ($plans as $plan) {
            $rows = $this->db->table('rate_plan_room_types rprt')
                ->select('room_types.id, room_types.name')
                ->join('room_types', 'room_types.id = rprt.room_type_id')
                ->where('rprt.rate_plan_id', $plan->id)
                ->get()->getResultArray();

            $plan->room_type_names = array_column($rows, 'name');
            $plan->room_type_ids   = array_map('intval', array_column($rows, 'id'));
        }

        return $plans;
    }

    /**
     * The active rate plan (if any) covering this room type on this date.
     * If more than one matches, the most recently created wins — flagged
     * as an assumption; the brief didn't specify a tie-break rule for
     * overlapping plans.
     */
    public function findActiveFor(int $roomTypeId, string $date): ?object
    {
        return $this->select('rate_plans.*')
            ->join('rate_plan_room_types', 'rate_plan_room_types.rate_plan_id = rate_plans.id')
            ->where('rate_plan_room_types.room_type_id', $roomTypeId)
            ->where('rate_plans.is_active', 1)
            ->where('rate_plans.date_start <=', $date)
            ->where('rate_plans.date_end >=', $date)
            ->orderBy('rate_plans.id', 'desc')
            ->first();
    }

    /**
     * Resolves what a room type actually costs on a given check-in date:
     * the matching rate plan's flat override / percentage adjustment, or
     * the room type's base_rate if no plan applies.
     */
    public function resolveRate(int $roomTypeId, string $date, string $baseRate): string
    {
        $plan = $this->findActiveFor($roomTypeId, $date);

        return $plan === null ? $baseRate : $this->applyPlan($plan, $baseRate);
    }

    private function applyPlan(object $plan, string $baseRate): string
    {
        if ($plan->adjustment_type === 'flat') {
            return number_format((float) $plan->adjustment_value, 2, '.', '');
        }

        $adjusted = (float) $baseRate * (1 + ((float) $plan->adjustment_value / 100));

        return number_format(max(0, $adjusted), 2, '.', '');
    }

    /**
     * Resolves the rate using one specific plan — a corporate account's
     * default_rate_plan_id — rather than date-matching. Only applies if
     * the plan is still active and actually covers this room type; falls
     * back to the normal date-based resolution otherwise, so an expired
     * or mismatched corporate plan never silently produces a nonsense
     * rate (see BookingWizardController::saveDetails()).
     */
    public function resolveWithPlan(int $planId, int $roomTypeId, string $date, string $baseRate): string
    {
        $plan = $this->find($planId);

        if ($plan === null || ! $plan->is_active || ! in_array($roomTypeId, $this->roomTypeIds($planId), true)) {
            return $this->resolveRate($roomTypeId, $date, $baseRate);
        }

        return $this->applyPlan($plan, $baseRate);
    }
}
