<?php

class MraEisOfflineThreshold {
    private $conn;

    public function __construct(PDO $conn) {
        $this->conn = $conn;
    }

    public static function thresholdFromTerminalConfiguration(array $configuration) {
        $offlineLimit = $configuration['offlineLimit'] ?? null;
        $hours = is_array($offlineLimit) ? ($offlineLimit['maxTransactionAgeInHours'] ?? null) : null;
        return is_numeric($hours) && (float)$hours > 0 ? (float)$hours : null;
    }

    public static function isConnectivityFailure(array $response) {
        return in_array((int)($response['status'] ?? 0), [0, 408, 500, 502, 503, 504], true);
    }

    public static function calculateWindow($thresholdHours, $offlineStartedAt, $currentTime = 'now') {
        $threshold = is_numeric($thresholdHours) ? (float)$thresholdHours : null;
        try {
            $started = is_string($offlineStartedAt) && trim($offlineStartedAt) !== ''
                ? new DateTimeImmutable(trim($offlineStartedAt), new DateTimeZone('UTC'))
                : null;
            $now = $currentTime instanceof DateTimeInterface
                ? DateTimeImmutable::createFromInterface($currentTime)->setTimezone(new DateTimeZone('UTC'))
                : new DateTimeImmutable((string)$currentTime, new DateTimeZone('UTC'));
        } catch (Throwable $e) {
            $started = null;
            $now = new DateTimeImmutable('now', new DateTimeZone('UTC'));
        }
        $expires = ($started && $threshold && $threshold > 0)
            ? $started->modify('+' . (int)round($threshold * 3600) . ' seconds')
            : null;
        return [
            'started' => $started,
            'expires' => $expires,
            'now' => $now,
            'remaining_seconds' => $expires ? max(0, $expires->getTimestamp() - $now->getTimestamp()) : 0,
            'expired' => !$threshold || !$started || !$expires || $now >= $expires,
        ];
    }

    public function getStatus($terminalId, $lock = false, $referenceTime = 'now') {
        $sql = 'SELECT c.*, t.branch_id, t.terminal_label FROM mra_eis_configurations c '
            . 'INNER JOIN mra_eis_terminals t ON t.id = c.terminal_local_id '
            . 'WHERE c.terminal_local_id = :terminal_id LIMIT 1' . ($lock ? ' FOR UPDATE' : '');
        $stmt = $this->conn->prepare($sql);
        $stmt->execute([':terminal_id' => (int)$terminalId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) {
            return null;
        }

        $threshold = is_numeric($row['offline_time_threshold_hours'] ?? null)
            ? (float)$row['offline_time_threshold_hours']
            : null;
        $window = self::calculateWindow($threshold, $row['offline_started_at'] ?? null, $referenceTime);
        $started = $window['started'];
        $now = $window['now'];
        $expires = $window['expires'];

        return [
            'terminal_id' => (int)$row['terminal_local_id'],
            'terminal_label' => (string)($row['terminal_label'] ?? ''),
            'branch_id' => (int)($row['branch_id'] ?? 0),
            'connection_status' => (string)($row['eis_connection_status'] ?? 'unknown'),
            'offline_time_threshold_hours' => $threshold,
            'offline_started_at' => $started ? $started->format(DateTimeInterface::ATOM) : null,
            'offline_expires_at' => $expires ? $expires->format(DateTimeInterface::ATOM) : null,
            'remaining_seconds' => $window['remaining_seconds'],
            'expired' => $window['expired'],
            'current_server_time' => $now->format(DateTimeInterface::ATOM),
            'last_successful_eis_connection_at' => $row['last_successful_eis_connection_at'] ?? null,
            'last_eis_sync_at' => $row['last_eis_sync_at'] ?? null,
        ];
    }

    public function assertOfflineSaleAllowed($terminalId, $userId = null, $saleReference = null, $referenceTime = 'now') {
        $status = $this->getStatus($terminalId, true, $referenceTime);
        if (!$status) {
            throw new RuntimeException('MRA EIS terminal configuration is missing. Offline sales are blocked.');
        }
        if ($status['connection_status'] !== 'offline') {
            return $status;
        }
        if ($status['expired']) {
            $message = 'The MRA EIS offline time limit has expired. Reconnect and synchronize with MRA EIS before recording another sale. '
                . 'Offline start: ' . ($status['offline_started_at'] ?: 'unavailable')
                . '; threshold: ' . ($status['offline_time_threshold_hours'] ?: 'invalid') . ' hours'
                . '; expiry: ' . ($status['offline_expires_at'] ?: 'unavailable')
                . '; sale reference time: ' . $status['current_server_time'] . '.';
            $this->audit($terminalId, 'offline_sale_blocked', $message, $userId, $saleReference);
            throw new RuntimeException($message);
        }
        return $status;
    }

    public function startOfflinePeriod($terminalId, $userId = null, $reason = null) {
        $stmt = $this->conn->prepare(
            "UPDATE mra_eis_configurations SET eis_connection_status = 'offline', "
            . 'offline_started_at = COALESCE(offline_started_at, UTC_TIMESTAMP()) '
            . 'WHERE terminal_local_id = :terminal_id AND offline_started_at IS NULL'
        );
        $stmt->execute([':terminal_id' => (int)$terminalId]);
        if ($stmt->rowCount() === 0) {
            return $this->getStatus($terminalId);
        }
        $status = $this->getStatus($terminalId);
        $this->audit($terminalId, 'offline_mode_started', $reason ?: 'MRA EIS connectivity failed.', $userId);
        return $status;
    }

    public function markTerminalOnline($terminalId, $userId = null) {
        $before = $this->getStatus($terminalId);
        $stmt = $this->conn->prepare(
            "UPDATE mra_eis_configurations SET eis_connection_status = 'online', "
            . 'last_successful_eis_connection_at = UTC_TIMESTAMP(), offline_started_at = NULL '
            . 'WHERE terminal_local_id = :terminal_id'
        );
        $stmt->execute([':terminal_id' => (int)$terminalId]);
        if ($before && $before['connection_status'] === 'offline') {
            $this->audit($terminalId, 'connectivity_restored', 'Authenticated MRA EIS communication succeeded; offline period cleared.', $userId);
            $this->audit($terminalId, 'offline_period_cleared', 'The terminal offline period was cleared after successful MRA EIS communication.', $userId);
        }
    }

    public function markSyncSuccessful($terminalId, $userId = null, $saleReference = null) {
        $stmt = $this->conn->prepare(
            'UPDATE mra_eis_configurations SET last_eis_sync_at = UTC_TIMESTAMP() WHERE terminal_local_id = :terminal_id'
        );
        $stmt->execute([':terminal_id' => (int)$terminalId]);
        $this->audit($terminalId, 'offline_sync_succeeded', 'Pending MRA EIS transaction synchronized.', $userId, $saleReference);
    }

    public function recordOfflineSaleAccepted($terminalId, $branchId, $userId, $saleReference) {
        $this->audit(
            $terminalId,
            'offline_sale_accepted',
            'Offline sale accepted for terminal ' . (int)$terminalId . ' at branch ' . (int)$branchId . '.',
            $userId,
            $saleReference
        );
    }

    public function pendingCount($terminalId) {
        $stmt = $this->conn->prepare(
            "SELECT COUNT(*) FROM mra_eis_outbox WHERE terminal_local_id = :terminal_id AND entity_type = 'sale' "
            . "AND status IN ('pending','ready','failed','processing')"
        );
        $stmt->execute([':terminal_id' => (int)$terminalId]);
        return (int)$stmt->fetchColumn();
    }

    private function audit($terminalId, $eventType, $message, $userId = null, $saleReference = null) {
        try {
            $stmt = $this->conn->prepare(
                'INSERT INTO mra_eis_compliance_events '
                . '(outbox_id, entity_type, source_key, event_type, message, payload_snapshot, created_by) '
                . "VALUES (NULL, 'terminal', :source_key, :event_type, :message, NULL, :created_by)"
            );
            $stmt->execute([
                ':source_key' => $saleReference ?: 'terminal:' . (int)$terminalId,
                ':event_type' => (string)$eventType,
                ':message' => (string)$message,
                ':created_by' => $userId ? (int)$userId : null,
            ]);
        } catch (Throwable $e) {
            error_log('MRA EIS offline audit write failed: ' . $e->getMessage());
        }
    }

    private function utcDate($value) {
        if (!is_string($value) || trim($value) === '') {
            return null;
        }
        try {
            return new DateTimeImmutable(trim($value), new DateTimeZone('UTC'));
        } catch (Throwable $e) {
            return null;
        }
    }
}
