<?php

namespace App\Libraries;

use App\Models\AiDailyBriefingModel;
use App\Models\BookingModel;
use App\Models\FolioLineItemModel;
use App\Models\GuestModel;
use App\Models\InventoryItemModel;
use App\Models\LoyaltyAccountModel;
use App\Models\MaintenanceWorkOrderModel;

/**
 * Assembles one tenant's morning briefing data (same "one model instance
 * per domain, real read-only aggregation methods, no new math" shape as
 * DashboardController::index()) and hands it to ClaudeService for plain-
 * language narration — Claude's job here is prose, never arithmetic.
 * Shared by the `ai:daily-briefing` scheduled command (all tenants) and
 * the Dashboard's "Generate Briefing" button (current tenant only, for
 * testing/on-demand use since this dev environment has no real cron),
 * so the two can never compute the briefing differently.
 */
class DailyBriefingService
{
    public function __construct(
        private readonly ClaudeService $claude,
    ) {
    }

    /**
     * @return array{success: bool, error: ?string, briefing: ?object}
     */
    public function generateForCurrentTenant(?int $triggeredBy = null, bool $force = false): array
    {
        $briefings = new AiDailyBriefingModel();
        $today     = date('Y-m-d');

        if (! $force) {
            $existing = $briefings->forDate($today);

            if ($existing !== null) {
                return ['success' => true, 'error' => null, 'briefing' => $existing];
            }
        }

        $data = $this->assembleData($today);

        $systemPrompt = 'You write a short morning briefing for a hotel manager. You will be given real data about '
            . "today's arrivals/departures, in-house VIP/loyalty guests, low-stock inventory, open maintenance work, "
            . "and yesterday's revenue by category. Write 4-6 sentences (or a short bullet list) highlighting what "
            . 'the manager should know today. Only ever reference names, numbers, and items actually present in the '
            . 'data given to you — never invent a guest, a figure, or an issue. If a category has nothing to report '
            . '(e.g. no low-stock items), you may skip it or note there is nothing to flag there.';

        $result = $this->claude->chat(
            'daily_briefing',
            $systemPrompt,
            [['role' => 'user', 'content' => json_encode($data)]],
            [],
            null,
            'daily_briefing',
            null,
            $triggeredBy,
        );

        if (! $result['success']) {
            return ['success' => false, 'error' => $result['error'], 'briefing' => null];
        }

        // upsert: a forced regeneration replaces today's row rather than
        // erroring on the unique briefing_date key or leaving two rows.
        $existing = $briefings->forDate($today);
        $row = ['briefing_date' => $today, 'summary_text' => $result['text'], 'data_snapshot' => json_encode($data)];

        if ($existing !== null) {
            $briefings->update($existing->id, $row);
        } else {
            $briefings->insert($row);
        }

        return ['success' => true, 'error' => null, 'briefing' => $briefings->forDate($today)];
    }

    private function assembleData(string $today): array
    {
        $yesterday = date('Y-m-d', strtotime($today . ' -1 day'));

        $bookings = new BookingModel();

        return [
            'date'       => $today,
            'arrivals'   => array_map(
                static fn ($b) => ['guest' => $b->guest_name, 'room' => $b->room_number, 'room_type' => $b->room_type_name],
                $bookings->arrivalsOn($today),
            ),
            'departures' => array_map(
                static fn ($b) => ['guest' => $b->guest_name, 'room' => $b->room_number, 'room_type' => $b->room_type_name],
                $bookings->departuresOn($today),
            ),
            'in_house_vip_or_loyalty_guests' => $this->inHouseVipOrLoyaltyGuests(),
            'low_stock_inventory' => array_map(
                static fn ($i) => ['name' => $i->name, 'quantity_in_stock' => (int) $i->quantity_in_stock, 'reorder_level' => (int) $i->reorder_level, 'unit' => $i->unit],
                (new InventoryItemModel())->lowStock(),
            ),
            'maintenance' => $this->maintenanceSummary(),
            'yesterday_revenue_by_category' => (new FolioLineItemModel())->byCategoryOn($yesterday),
        ];
    }

    /**
     * Today's checked-in (in-house) guests who are either flagged VIP
     * (guests.segment) or hold a silver/gold loyalty tier — two distinct,
     * real signals this app already tracks (see GuestModel's `segment`
     * column and LoyaltyAccountModel's `tier`), not a single fabricated
     * "importance" score.
     */
    private function inHouseVipOrLoyaltyGuests(): array
    {
        $bookings = (new BookingModel())->withDetails()
            ->where('bookings.status', 'checked_in')
            ->findAll();

        if ($bookings === []) {
            return [];
        }

        $guestIds     = array_unique(array_map(static fn ($b) => (int) $b->guest_id, $bookings));
        $guests       = (new GuestModel())->whereIn('id', $guestIds)->findAll();
        $guestsById   = [];
        foreach ($guests as $g) {
            $guestsById[$g->id] = $g;
        }
        $loyaltyByGuest = (new LoyaltyAccountModel())->forGuests($guestIds);

        $notable = [];

        foreach ($bookings as $b) {
            $guest   = $guestsById[$b->guest_id] ?? null;
            $loyalty = $loyaltyByGuest[$b->guest_id] ?? null;
            $isVip   = $guest !== null && $guest->segment === 'vip';
            $isTiered = $loyalty !== null && in_array($loyalty->tier, ['silver', 'gold'], true);

            if ($isVip || $isTiered) {
                $notable[] = [
                    'guest'         => $b->guest_name,
                    'room'          => $b->room_number,
                    'vip'           => $isVip,
                    'loyalty_tier'  => $loyalty->tier ?? null,
                ];
            }
        }

        return $notable;
    }

    private function maintenanceSummary(): array
    {
        $orders = new MaintenanceWorkOrderModel();

        $overdueList = $orders->withDetails()
            ->whereIn('maintenance_work_orders.status', ['open', 'in_progress'])
            ->where('maintenance_work_orders.due_date IS NOT NULL', null, false)
            ->where('maintenance_work_orders.due_date <', date('Y-m-d'))
            ->orderBy('maintenance_work_orders.due_date', 'asc')
            ->limit(5)
            ->findAll();

        return [
            'open_count'    => $orders->countByStatus('open'),
            'overdue_count' => $orders->countOverdue(),
            'overdue_examples' => array_map(
                static fn ($o) => ['title' => $o->title, 'due_date' => $o->due_date, 'priority' => $o->priority],
                $overdueList,
            ),
        ];
    }
}
