<?php

namespace App\Models;

use App\Libraries\PhoneNumber;
use Config\Lookups;

class GuestModel extends TenantModel
{
    protected $table            = 'guests';
    protected $primaryKey       = 'id';
    protected $useAutoIncrement = true;
    protected $returnType       = 'object';
    protected $useTimestamps    = true;

    protected $allowedFields = [
        'full_name',
        'phone',
        'email',
        'id_type',
        'id_number',
        'nationality',
        'segment',
        'is_blacklisted',
        'address',
        'date_of_birth',
        'anniversary_date',
        'notes',
    ];

    // Normalize phone at the point of creation/edit — not just on display —
    // so dedup checks and (later) WhatsApp/SMS sending can trust the field.
    protected $beforeInsert = ['normalizePhone'];
    protected $beforeUpdate = ['normalizePhone'];

    public function __construct()
    {
        parent::__construct();

        $idTypes  = array_keys((new Lookups())->guestIdTypes);
        $segments = array_keys((new Lookups())->guestSegments);

        $this->validationRules = [
            'full_name' => 'required|max_length[150]',
            'phone'     => 'required|validPhone',
            'email'     => 'permit_empty|valid_email|max_length[150]',
            'id_type'   => 'permit_empty|in_list[' . implode(',', $idTypes) . ']',
            'segment'   => 'permit_empty|in_list[' . implode(',', $segments) . ']',
        ];
    }

    protected function normalizePhone(array $data): array
    {
        if (isset($data['data']['phone'])) {
            $normalized = PhoneNumber::normalize($data['data']['phone']);

            if ($normalized !== null) {
                $data['data']['phone'] = $normalized;
            }
        }

        return $data;
    }

    /**
     * Warns (doesn't block — two guests can legitimately share a phone,
     * e.g. a family booking under one contact number) when a normalized
     * phone or an ID number already belongs to another guest.
     *
     * @return list<object>
     */
    public function findPossibleDuplicates(string $phone, ?string $idNumber, ?int $excludeId = null): array
    {
        $normalizedPhone = PhoneNumber::normalize($phone);

        $builder = $this->groupStart();

        if ($normalizedPhone !== null) {
            $builder->orWhere('phone', $normalizedPhone);
        }

        if (! empty($idNumber)) {
            $builder->orWhere('id_number', $idNumber);
        }

        $builder->groupEnd();

        if ($excludeId !== null) {
            $builder->where('id !=', $excludeId);
        }

        return $normalizedPhone === null && empty($idNumber) ? [] : $builder->findAll();
    }

    public function search(string $term)
    {
        return $this->groupStart()
            ->like('full_name', $term)
            ->orLike('phone', $term)
            ->orLike('id_number', $term)
            ->groupEnd();
    }

    public function countBySegment(string $segment): int
    {
        return $this->where('segment', $segment)->countAllResults();
    }

    /**
     * The Guest CRM's list/filters — mirrors BookingModel::search() (same
     * "return $this chainable, controller adds ->paginate()" shape).
     *
     * @param array{q?: string, segment?: string, nationality?: string, tab?: string} $filters
     */
    public function filterList(array $filters): static
    {
        $builder = ! empty($filters['q']) ? $this->search($filters['q']) : $this;

        if (! empty($filters['segment'])) {
            $builder->where('segment', $filters['segment']);
        }

        if (! empty($filters['nationality'])) {
            $builder->where('nationality', $filters['nationality']);
        }

        switch ($filters['tab'] ?? 'all') {
            case 'vip':
                $builder->where('segment', 'vip');
                break;
            case 'repeat':
                $builder->whereIn('id', $this->repeatGuestIds() ?: [0]);
                break;
            case 'blacklist':
                $builder->where('is_blacklisted', 1);
                break;
            case 'all':
            default:
                break;
        }

        return $builder->orderBy('full_name', 'asc');
    }

    /** @return list<string> */
    public function distinctNationalities(): array
    {
        $rows = $this->select('nationality')
            ->where('nationality IS NOT NULL', null, false)
            ->where('nationality !=', '')
            ->distinct()
            ->orderBy('nationality', 'asc')
            ->findAll();

        return array_column($rows, 'nationality');
    }

    public function countBlacklisted(): int
    {
        return $this->where('is_blacklisted', 1)->countAllResults();
    }

    /**
     * Distinct nationalities on file — the Guest CRM's "Countries" stat.
     * Guests without one set (permit_empty on this field) don't count.
     */
    public function countCountries(): int
    {
        return count($this->select('nationality')
            ->where('nationality IS NOT NULL', null, false)
            ->where('nationality !=', '')
            ->distinct()
            ->findAll());
    }

    /**
     * Guest ids with 2+ non-cancelled/no-show bookings — "repeat guests"
     * for the Guest CRM stat cards/tab. Built via the raw bookings table
     * rather than a join back onto this model, same reasoning as
     * FolioModel's totalCharges()/totalPaid() sub-queries.
     *
     * @return list<int>
     */
    public function repeatGuestIds(): array
    {
        $rows = $this->db->table('bookings')
            ->select('guest_id, COUNT(*) as stay_count')
            ->whereIn('status', ['pending_deposit', 'confirmed', 'checked_in', 'checked_out'])
            ->groupBy('guest_id')
            ->get()
            ->getResultArray();

        $ids = [];

        foreach ($rows as $row) {
            if ((int) $row['stay_count'] >= 2) {
                $ids[] = (int) $row['guest_id'];
            }
        }

        return $ids;
    }

    /**
     * Repeat-guest rate as a percentage of every guest on file for the
     * Reports KPI dashboard — same "repeat" definition repeatGuestIds()
     * already uses (2+ real stays), just expressed as a rate rather than
     * a raw id list.
     */
    public function repeatGuestRate(): float
    {
        $total = $this->countAllResults();

        if ($total === 0) {
            return 0.0;
        }

        return round(count($this->repeatGuestIds()) / $total * 100, 1);
    }

    /**
     * All-time total paid + count of guests who've paid something — the
     * Reports KPI dashboard divides these into an average (same bcdiv
     * pattern ReportController already uses for ADR/RevPAR, kept out of
     * this method so the division stays in one place). Guests with zero
     * real payments don't drag the average down, matching how a single
     * guest's own "lifetime value" is defined in GuestController::show().
     *
     * @return array{total: string, guestCount: int}
     */
    public function lifetimeSpendTotals(): array
    {
        $rows = $this->db->table('folios')
            ->select('folios.guest_id, SUM(CASE WHEN folio_payments.is_refund = 0 THEN folio_payments.amount ELSE -folio_payments.amount END) as paid')
            ->join('folio_payments', 'folio_payments.folio_id = folios.id')
            ->groupBy('folios.guest_id')
            ->get()
            ->getResultArray();

        $total = '0.00';

        foreach ($rows as $row) {
            $total = money_add($total, (string) $row['paid']);
        }

        return ['total' => $total, 'guestCount' => count($rows)];
    }
}
