<?php

namespace App\Models;

use Config\Lookups;
use RuntimeException;

/**
 * Settlement is the piece that ties the POS module back into Phase 1's
 * folio system rather than forking a parallel billing table (per the
 * Phase 2 brief's explicit instruction): every settled order ends up as
 * exactly one `restaurant`-category folio_line_item, either on a brand
 * new standalone folio (direct payment) or the guest's existing open
 * stay folio (charge to room) — see settleDirect()/settleToRoom().
 */
class PosOrderModel extends TenantModel
{
    protected $table            = 'pos_orders';
    protected $primaryKey       = 'id';
    protected $useAutoIncrement = true;
    protected $returnType       = 'object';
    protected $useTimestamps    = true;

    protected $allowedFields = [
        'order_type',
        'dining_table_id',
        'guest_id',
        'corporate_account_id',
        'status',
        'folio_id',
        'opened_by',
    ];

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

        $lookups = new Lookups();

        $this->validationRules = [
            'order_type' => 'required|in_list[' . implode(',', array_keys($lookups->posOrderTypes)) . ']',
            'status'     => 'permit_empty|in_list[' . implode(',', array_keys($lookups->posOrderStatuses)) . ']',
        ];
    }

    public function withDetails()
    {
        return $this->select("pos_orders.*, dining_tables.table_number as table_number,
                guests.full_name as guest_name, guests.segment as guest_segment,
                dining_memberships.tier as dining_tier")
            ->join('dining_tables', 'dining_tables.id = pos_orders.dining_table_id', 'left')
            ->join('guests', 'guests.id = pos_orders.guest_id', 'left')
            ->join('dining_memberships', "dining_memberships.guest_id = pos_orders.guest_id AND dining_memberships.status = 'active'", 'left');
    }

    /** Open/sent_to_kitchen/served — anything not yet settled. */
    public function activeOrders(): array
    {
        return $this->withDetails()->whereIn('pos_orders.status', ['open', 'sent_to_kitchen', 'served'])
            ->orderBy('pos_orders.created_at', 'desc')->findAll();
    }

    public function kitchenQueue(): array
    {
        return $this->withDetails()->where('pos_orders.status', 'sent_to_kitchen')
            ->orderBy('pos_orders.created_at')->findAll();
    }

    public function sendToKitchen(int $orderId): bool
    {
        return $this->update($orderId, ['status' => 'sent_to_kitchen']);
    }

    public function markServed(int $orderId): bool
    {
        return $this->update($orderId, ['status' => 'served']);
    }

    /**
     * Pay directly: cash/card/mobile money, no room involved. Opens a
     * fresh standalone folio (booking_id null) for the transaction, posts
     * the order as one `restaurant` line item, records the payment, and
     * closes the folio immediately since it's fully settled in one shot.
     */
    public function settleDirect(int $orderId, int $guestId, string $method, int $recordedBy, ?int $corporateAccountId = null): int
    {
        $order = $this->find($orderId);

        if ($order === null) {
            throw new RuntimeException('Order not found.');
        }

        $total = (new PosOrderItemModel())->orderTotal($orderId);

        $this->db->transStart();

        $folios  = new FolioModel();
        $folioId = $folios->openStandalone($guestId);

        if ($corporateAccountId !== null) {
            $folios->setCorporateAccount($folioId, $corporateAccountId);
        }

        (new FolioLineItemModel())->insert([
            'folio_id'    => $folioId,
            'description' => "Restaurant order #{$orderId}",
            'amount'      => $total,
            'category'    => 'restaurant',
            'item_date'   => date('Y-m-d'),
            'created_by'  => $recordedBy,
        ]);

        (new FolioPaymentModel())->insert([
            'folio_id'    => $folioId,
            'amount'      => $total,
            'method'      => $method,
            'reference'   => null,
            'recorded_by' => $recordedBy,
            'paid_at'     => date('Y-m-d H:i:s'),
        ]);

        $folios->close($folioId);

        $this->update($orderId, ['status' => 'paid', 'folio_id' => $folioId, 'guest_id' => $guestId, 'corporate_account_id' => $corporateAccountId]);

        $this->db->transComplete();

        return $folioId;
    }

    /**
     * Charge to room: posts the order as a `restaurant` line item onto the
     * guest's existing open stay folio — no payment recorded here, exactly
     * like Phase 1's own nightly room charges. The guest settles it at
     * checkout along with everything else.
     */
    public function settleToRoom(int $orderId, int $guestId, int $recordedBy): int
    {
        $order = $this->find($orderId);

        if ($order === null) {
            throw new RuntimeException('Order not found.');
        }

        $folio = (new FolioModel())->findOpenForGuest($guestId);

        if ($folio === null) {
            throw new RuntimeException('This guest has no open room folio to charge to — they must be checked in.');
        }

        $total = (new PosOrderItemModel())->orderTotal($orderId);

        $this->db->transStart();

        (new FolioLineItemModel())->insert([
            'folio_id'    => $folio->id,
            'description' => "Restaurant order #{$orderId} — charged to room",
            'amount'      => $total,
            'category'    => 'restaurant',
            'item_date'   => date('Y-m-d'),
            'created_by'  => $recordedBy,
        ]);

        $this->update($orderId, ['status' => 'paid', 'folio_id' => $folio->id, 'guest_id' => $guestId]);

        $this->db->transComplete();

        return $folio->id;
    }

    /** Used by StaffPerformanceController — orders opened per F&B staff member in a date range. */
    public function countByOpener(string $from, string $to): array
    {
        $rows = $this->select('opened_by, COUNT(*) as order_count')
            ->where('created_at >=', $from . ' 00:00:00')
            ->where('created_at <=', $to . ' 23:59:59')
            ->where('opened_by IS NOT NULL')
            ->groupBy('opened_by')
            ->findAll();

        $byUser = [];

        foreach ($rows as $row) {
            $byUser[$row->opened_by] = (int) $row->order_count;
        }

        return $byUser;
    }
}
