<?php

namespace objects;

use classes\db;
use classes\object_property;
use classes\selfserve;
use classes\selfserve_schema_bootstrap;
use DateTime;
use modules\selfserve\helpers\selfserve_wash_session_status;
use traits\db_object_t;

class selfserve_wash_sessions_o extends db
{
    use db_object_t;

    public const TERMINAL_STATUSES = [
        'COMPLETED',
        'FORCE_STOPPED',
    ];

    public object_property $lane_id;
    public object_property $department_id;
    public object_property $machine_type_id;
    public object_property $customer_number;
    public object_property $subuser_id;
    public object_property $vehicle_id;
    public object_property $vehicle_type_id;
    public object_property $reg;
    public object_property $status;
    public object_property $allowed;
    public object_property $machine_relay_enabled;
    public object_property $machine_relay_enabled_at;
    public object_property $machine_start_triggered;
    public object_property $machine_start_triggered_at;
    public object_property $wash_started_at;
    public object_property $order_id;
    public object_property $completed_at;
    public object_property $metadata_json;
    public object_property $created_at;
    public object_property $updated_at;
    public object_property $deleted_at;

    public function structure(): void
    {
        selfserve_schema_bootstrap::ensureTables();
        $this->setTable('selfserve_wash_sessions');
    }

    public function add(
        int $laneId,
        int $departmentId,
        ?int $machineTypeId,
        ?int $customerNumber,
        string $reg,
        ?int $vehicleId,
        ?int $vehicleTypeId,
        selfserve_wash_session_status $status,
        bool $allowed = false,
        ?array $metadata = null,
        ?int $subuserId = null
    ): self {
        $this->id = self::add_object([
            'lane_id' => $laneId,
            'department_id' => $departmentId,
            'machine_type_id' => $machineTypeId,
            'customer_number' => $customerNumber,
            'subuser_id' => $subuserId !== null && $subuserId > 0 ? $subuserId : null,
            'vehicle_id' => $vehicleId,
            'vehicle_type_id' => $vehicleTypeId,
            'reg' => selfserve::standardize_registration($reg),
            'status' => $status->value,
            'allowed' => $allowed,
            'metadata_json' => $metadata,
        ]);
        $this->getObjectProperties();
        $this->objectChanged();

        return $this;
    }

    public function getObjectProperties(): void
    {
        $this->lane_id = new object_property($this->table, $this->id, 'lane_id', 'int', false);
        $this->department_id = new object_property($this->table, $this->id, 'department_id', 'int', false);
        $this->machine_type_id = new object_property($this->table, $this->id, 'machine_type_id', 'int', false);
        $this->customer_number = new object_property($this->table, $this->id, 'customer_number', 'int', false);
        $this->subuser_id = new object_property($this->table, $this->id, 'subuser_id', 'int', false);
        $this->vehicle_id = new object_property($this->table, $this->id, 'vehicle_id', 'int', false);
        $this->vehicle_type_id = new object_property($this->table, $this->id, 'vehicle_type_id', 'int', false);
        $this->reg = new object_property($this->table, $this->id, 'reg', 'string', false);
        $this->status = new object_property($this->table, $this->id, 'status', 'string', false);
        $this->allowed = new object_property($this->table, $this->id, 'allowed', 'bool', false);
        $this->machine_relay_enabled = new object_property($this->table, $this->id, 'machine_relay_enabled', 'bool', false);
        $this->machine_relay_enabled_at = new object_property($this->table, $this->id, 'machine_relay_enabled_at', 'datetime', false);
        $this->machine_start_triggered = new object_property($this->table, $this->id, 'machine_start_triggered', 'bool', false);
        $this->machine_start_triggered_at = new object_property($this->table, $this->id, 'machine_start_triggered_at', 'datetime', false);
        $this->wash_started_at = new object_property($this->table, $this->id, 'wash_started_at', 'datetime', false);
        $this->order_id = new object_property($this->table, $this->id, 'order_id', 'int', false);
        $this->completed_at = new object_property($this->table, $this->id, 'completed_at', 'datetime', false);
        $this->metadata_json = new object_property($this->table, $this->id, 'metadata_json', 'json', false);
        $this->created_at = new object_property($this->table, $this->id, 'created_at', 'timestamp', false);
        $this->updated_at = new object_property($this->table, $this->id, 'updated_at', 'timestamp', false);
        $this->deleted_at = new object_property($this->table, $this->id, 'deleted_at', 'timestamp', false);
    }

    public function objectChanged(): void
    {
        // No dedicated cache invalidation yet.
    }

    public function updateStatus(selfserve_wash_session_status $status): void
    {
        $this->status->set($status->value);
    }

    public static function isTerminalStatus(?string $status): bool
    {
        return in_array(strtoupper(trim((string)$status)), self::TERMINAL_STATUSES, true);
    }

    public static function terminalStatusSqlList(): string
    {
        return "'" . implode("','", array_map(
            static fn(string $status): string => str_replace("'", "''", $status),
            self::TERMINAL_STATUSES
        )) . "'";
    }

    public function isOpen(): bool
    {
        return $this->completed_at->value() === null
            && !self::isTerminalStatus((string)$this->status->value());
    }

    public function markRelayEnabled(): void
    {
        $now = date('Y-m-d H:i:s');
        $this->machine_relay_enabled->set(true);
        $this->machine_relay_enabled_at->set($now);
        $this->status->set(selfserve_wash_session_status::MACHINE_RELAY_ENABLED->value);
    }

    public function markRelayDisabled(): void
    {
        $this->machine_relay_enabled->set(false);
        $this->machine_relay_enabled_at->set(null);
    }

    public function markMachineStartTriggered(?string $washStartedAt = null): void
    {
        $now = date('Y-m-d H:i:s');
        $resolvedWashStartedAt = $washStartedAt ?? $now;
        $this->machine_start_triggered->set(true);
        $this->machine_start_triggered_at->set($now);
        $this->wash_started_at->set($resolvedWashStartedAt);
        if ((bool)$this->machine_relay_enabled->value() !== true) {
            $this->machine_relay_enabled->set(true);
            $this->machine_relay_enabled_at->set($now);
        }
        $this->status->set(selfserve_wash_session_status::MACHINE_STARTED->value);
    }

    public function markCompleted(?int $orderId = null): void
    {
        if (!$this->closeIfOpen(selfserve_wash_session_status::COMPLETED, $orderId)) {
            return;
        }
    }

    public function markCompletedIfOpen(?int $orderId = null): bool
    {
        return $this->closeIfOpen(selfserve_wash_session_status::COMPLETED, $orderId);
    }

    private function markCompletedInMemory(?int $orderId = null): void
    {
        $this->completed_at->set(date('Y-m-d H:i:s'));
        if ($orderId !== null) {
            $this->order_id->set($orderId);
        }
        $this->status->set(selfserve_wash_session_status::COMPLETED->value);
    }

    public function markForceStopped(?int $orderId = null, ?array $metadata = null): void
    {
        if (!$this->closeIfOpen(selfserve_wash_session_status::FORCE_STOPPED, $orderId, $metadata)) {
            return;
        }
    }

    public function markForceStoppedIfOpen(?int $orderId = null, ?array $metadata = null): bool
    {
        return $this->closeIfOpen(selfserve_wash_session_status::FORCE_STOPPED, $orderId, $metadata);
    }

    private function markForceStoppedInMemory(?int $orderId = null, ?array $metadata = null): void
    {
        $this->completed_at->set(date('Y-m-d H:i:s'));
        if ($orderId !== null) {
            $this->order_id->set($orderId);
        }
        if ($metadata !== null) {
            $existing = $this->metadata_json->value();
            $existing = is_array($existing) ? $existing : [];
            $existing['force_stop'] = $metadata;
            $this->metadata_json->set($existing);
        }
        $this->status->set(selfserve_wash_session_status::FORCE_STOPPED->value);
    }

    private function closeIfOpen(selfserve_wash_session_status $status, ?int $orderId = null, ?array $metadata = null): bool
    {
        if (!$this->isPersistedSession()) {
            if ($status === selfserve_wash_session_status::COMPLETED) {
                $this->markCompletedInMemory($orderId);
            } else {
                $this->markForceStoppedInMemory($orderId, $metadata);
            }
            return true;
        }

        global $db;

        $updates = [
            "`completed_at` = NOW()",
            "`status` = '" . $db->escape_string($status->value) . "'",
        ];

        if ($orderId !== null) {
            $updates[] = "`order_id` = " . (int)$orderId;
        }

        if ($metadata !== null) {
            $existing = $this->metadata_json->value();
            $existing = is_array($existing) ? $existing : [];
            $existing['force_stop'] = $metadata;
            $updates[] = "`metadata_json` = '" . $db->escape_string(json_encode($existing, JSON_THROW_ON_ERROR)) . "'";
        }

        $terminalStatuses = self::terminalStatusSqlList();
        $db->query(
            "UPDATE `selfserve_wash_sessions` SET " . implode(', ', $updates) .
            " WHERE `id` = " . (int)$this->id .
            " AND `completed_at` IS NULL" .
            " AND UPPER(TRIM(`status`)) NOT IN ($terminalStatuses)"
        );

        $changed = $db->conn()->affected_rows > 0;
        $this->select((int)$this->id);
        return $changed;
    }

    private function isPersistedSession(): bool
    {
        return isset($this->id) && (int)$this->id > 0 && $this->exists();
    }

    public function selectLatestOpenByLane(int $laneId, ?int $customerNumber = null, ?int $subuserId = null): self
    {
        $filters = [
            'lane_id' => $laneId,
            'completed_at' => null,
            'deleted_at' => null,
        ];
        if ($customerNumber !== null) {
            $filters['customer_number'] = $customerNumber;
        }
        if ($subuserId !== null) {
            $filters['subuser_id'] = $subuserId;
        }
        $rows = $this->getFieldsWhere($filters, ['id', 'status']);
        $rows = array_values(array_filter(
            $rows,
            static fn(array $row): bool => !self::isTerminalStatus($row['status'] ?? null)
        ));
        if ($rows === []) {
            return $this;
        }
        usort($rows, static fn(array $a, array $b): int => (int)$b['id'] <=> (int)$a['id']);
        $this->select((int)$rows[0]['id']);
        return $this;
    }

    public function selectLatestOpenByLaneAndReg(int $laneId, string $reg, ?int $customerNumber = null, ?int $subuserId = null): self
    {
        $filters = [
            'lane_id' => $laneId,
            'reg' => selfserve::standardize_registration($reg),
            'completed_at' => null,
            'deleted_at' => null,
        ];
        if ($customerNumber !== null) {
            $filters['customer_number'] = $customerNumber;
        }
        if ($subuserId !== null) {
            $filters['subuser_id'] = $subuserId;
        }
        $rows = $this->getFieldsWhere($filters, ['id', 'status']);
        $rows = array_values(array_filter(
            $rows,
            static fn(array $row): bool => !self::isTerminalStatus($row['status'] ?? null)
        ));
        if ($rows === []) {
            return $this;
        }
        usort($rows, static fn(array $a, array $b): int => (int)$b['id'] <=> (int)$a['id']);
        $this->select((int)$rows[0]['id']);
        return $this;
    }

    public function selectLatestByLaneAndReg(int $laneId, string $reg): self
    {
        $rows = $this->getFieldsWhere([
            'lane_id' => $laneId,
            'reg' => selfserve::standardize_registration($reg),
            'deleted_at' => null,
        ], ['id']);
        if ($rows === []) {
            return $this;
        }
        usort($rows, static fn(array $a, array $b): int => (int)$b['id'] <=> (int)$a['id']);
        $this->select((int)$rows[0]['id']);
        return $this;
    }

    public function asArray(): array
    {
        return [
            'id' => (int)$this->id,
            'lane_id' => (int)$this->lane_id->value(),
            'department_id' => (int)$this->department_id->value(),
            'machine_type_id' => $this->machine_type_id->value() === null ? null : (int)$this->machine_type_id->value(),
            'customer_number' => $this->customer_number->value() === null ? null : (int)$this->customer_number->value(),
            'subuser_id' => $this->subuser_id->value() === null ? null : (int)$this->subuser_id->value(),
            'vehicle_id' => $this->vehicle_id->value() === null ? null : (int)$this->vehicle_id->value(),
            'vehicle_type_id' => $this->vehicle_type_id->value() === null ? null : (int)$this->vehicle_type_id->value(),
            'reg' => (string)$this->reg->value(),
            'status' => (string)$this->status->value(),
            'allowed' => (bool)$this->allowed->value(),
            'machine_relay_enabled' => (bool)$this->machine_relay_enabled->value(),
            'machine_relay_enabled_at' => $this->machine_relay_enabled_at->value() === null ? null : (string)$this->machine_relay_enabled_at->value(),
            'machine_start_triggered' => (bool)$this->machine_start_triggered->value(),
            'machine_start_triggered_at' => $this->machine_start_triggered_at->value() === null ? null : (string)$this->machine_start_triggered_at->value(),
            'wash_started_at' => $this->wash_started_at->value() === null ? null : (string)$this->wash_started_at->value(),
            'order_id' => $this->order_id->value() === null ? null : (int)$this->order_id->value(),
            'completed_at' => $this->completed_at->value() === null ? null : (string)$this->completed_at->value(),
            'metadata' => (array)($this->metadata_json->value() ?? []),
            'open' => $this->isOpen(),
            'created_at' => (string)$this->created_at->value(),
            'updated_at' => $this->updated_at->value() === null ? null : (string)$this->updated_at->value(),
        ];
    }

    public function getElapsedMinutes(): int
    {
        $startAt = $this->wash_started_at->value() ?? $this->machine_start_triggered_at->value();
        if ($startAt === null || trim((string)$startAt) === '') {
            return 0;
        }

        try {
            $start = new DateTime((string)$startAt);
            $endAt = $this->completed_at->value() ?? date('Y-m-d H:i:s');
            $end = new DateTime((string)$endAt);
        } catch (\Throwable) {
            return 0;
        }

        if ($start > $end) {
            return 0;
        }

        $diff = $start->diff($end);
        return (int)(($diff->days * 24 * 60) + ($diff->h * 60) + $diff->i);
    }
}
