<?php
namespace EasyChat\Library;









class AppClock {
    const SETTING_TIMEZONE = 'chat_runtime_timezone';

    private $registry;
    private static $timezone_name = null;

    public function __construct($registry = null) {
        $this->registry = $registry;
    }

    public function nowSql(): string {
        return (new \DateTimeImmutable('now', $this->timezone()))->format('Y-m-d H:i:s');
    }

    public function fromUnixTimestampSql(int $timestamp): string {
        if ($timestamp <= 0) {
            return '';
        }

        try {
            return (new \DateTimeImmutable('@' . $timestamp))
                ->setTimezone($this->timezone())
                ->format('Y-m-d H:i:s');
        } catch (\Throwable $e) {
            return '';
        }
    }

    public function fromDateStringSql(string $value): string {
        $value = trim($value);
        if ($value === '') {
            return '';
        }

        try {
             
             
             
            return (new \DateTimeImmutable($value, $this->timezone()))
                ->setTimezone($this->timezone())
                ->format('Y-m-d H:i:s');
        } catch (\Throwable $e) {
            return '';
        }
    }

    public function sqlToTimestamp(string $value) {
        $value = trim($value);
        if ($value === '' || !preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $value)) {
            return false;
        }

        $date = \DateTimeImmutable::createFromFormat('!Y-m-d H:i:s', $value, $this->timezone());
        return $date instanceof \DateTimeImmutable ? $date->getTimestamp() : false;
    }

    public function timestampToSql(int $timestamp): string {
        if ($timestamp <= 0) {
            return '';
        }

        return (new \DateTimeImmutable('@' . $timestamp))
            ->setTimezone($this->timezone())
            ->format('Y-m-d H:i:s');
    }

    public function fromProviderTimestampSql($created, string $published_at = ''): string {
        $timestamp = $this->normalizeUnixTimestamp($created);
        if ($timestamp > 0) {
            return $this->fromUnixTimestampSql($timestamp);
        }

        return $this->fromDateStringSql($published_at);
    }

    public function sqlToIsoUtc(string $value): string {
        $timestamp = $this->sqlToTimestamp($value);
        if ($timestamp === false) {
            return $this->nowIsoUtc();
        }

        return (new \DateTimeImmutable('@' . $timestamp))
            ->setTimezone(new \DateTimeZone('UTC'))
            ->format('Y-m-d\TH:i:s\Z');
    }

    public function nowIsoUtc(): string {
        return (new \DateTimeImmutable('now', new \DateTimeZone('UTC')))->format('Y-m-d\TH:i:s\Z');
    }

    public function secondsBetweenSql(string $from, string $to): int {
        $from_ts = $this->sqlToTimestamp($from);
        $to_ts = $this->sqlToTimestamp($to);
        if ($from_ts === false || $to_ts === false) {
            return 0;
        }

        return max(0, $to_ts - $from_ts);
    }

    public function secondsSinceSql(string $from): int {
        $from_ts = $this->sqlToTimestamp($from);
        if ($from_ts === false) {
            return 0;
        }

        return max(0, time() - $from_ts);
    }

    public function databaseTimezoneOffset(): string {
        $offset = (new \DateTimeImmutable('now', $this->timezone()))->getOffset();
        $sign = $offset < 0 ? '-' : '+';
        $abs = abs($offset);
        return sprintf('%s%02d:%02d', $sign, intdiv($abs, 3600), intdiv($abs % 3600, 60));
    }

    public function syncDatabaseSessionTimezone(): void {
        $db = $this->db();
        if (!$db) {
            return;
        }

        $offset = $this->databaseTimezoneOffset();
        try {
            if (method_exists($db, 'setSessionTimezoneOffset') && $db->setSessionTimezoneOffset($offset)) {
                return;
            }
            $db->query("SET time_zone = '" . $db->escape($offset) . "'");
        } catch (\Throwable $e) {
             
             
             
        }
    }

    public static function resetTimezoneCache(): void {
        self::$timezone_name = null;
    }

    public function timezoneName(): string {
        if (self::$timezone_name !== null) {
            return self::$timezone_name;
        }

        foreach ([
            $this->settingsTimezone(),
            defined('EASYCHAT_TIMEZONE') ? (string)EASYCHAT_TIMEZONE : '',
        ] as $candidate) {
            $candidate = trim((string)$candidate);
            if ($this->isValidTimezone($candidate)) {
                self::$timezone_name = $candidate;
                return self::$timezone_name;
            }
        }

        $db_timezone = $this->databaseNamedTimezone();
        if ($db_timezone !== '') {
            self::$timezone_name = $db_timezone;
            return self::$timezone_name;
        }

        $php_timezone = date_default_timezone_get();
        if ($this->isValidTimezone($php_timezone) && strtoupper($php_timezone) !== 'UTC') {
            self::$timezone_name = $php_timezone;
            return self::$timezone_name;
        }

        $db_offset_timezone = $this->databaseOffsetTimezone();
        if ($db_offset_timezone !== '') {
            self::$timezone_name = $db_offset_timezone;
            return self::$timezone_name;
        }

        self::$timezone_name = $this->isValidTimezone($php_timezone) ? $php_timezone : 'UTC';
        return self::$timezone_name;
    }

    private function timezone(): \DateTimeZone {
        return new \DateTimeZone($this->timezoneName());
    }

    private function normalizeUnixTimestamp($value): int {
        if (!is_scalar($value)) {
            return 0;
        }

        $value = trim((string)$value);
        if ($value === '' || !preg_match('/^\d+$/', $value)) {
            return 0;
        }

        $timestamp = (int)$value;
        if ($timestamp > 999999999999) {
            $timestamp = (int)floor($timestamp / 1000);
        }

        return $timestamp > 0 ? $timestamp : 0;
    }

    private function settingsTimezone(): string {
        $db = $this->db();
        if (!$db || !function_exists('easychat_db_prefix')) {
            return '';
        }

        try {
            $prefix = easychat_db_prefix();
            $query = $db->query(
                "SELECT `value` FROM `{$prefix}settings` WHERE `key` = '" . $db->escape(self::SETTING_TIMEZONE) . "' LIMIT 1"
            );
            if ($query && !empty($query->num_rows)) {
                return trim((string)($query->row['value'] ?? ''));
            }
        } catch (\Throwable $e) {
        }

        return '';
    }

    private function databaseNamedTimezone(): string {
        $db = $this->db();
        if (!$db) {
            return '';
        }

        try {
            $query = $db->query('SELECT @@global.time_zone AS global_time_zone');
            $timezone = trim((string)($query->row['global_time_zone'] ?? ''));
        } catch (\Throwable $e) {
            return '';
        }

        if ($timezone === '' || strtoupper($timezone) === 'SYSTEM') {
            return '';
        }

        return $this->isValidTimezone($timezone) ? $timezone : '';
    }

    private function databaseOffsetTimezone(): string {
        $db = $this->db();
        if (!$db) {
            return '';
        }

        try {
            $query = $db->query('SELECT TIMESTAMPDIFF(SECOND, UTC_TIMESTAMP(), NOW()) AS offset_seconds');
            $offset = isset($query->row['offset_seconds']) ? (int)$query->row['offset_seconds'] : 0;
        } catch (\Throwable $e) {
            return '';
        }

        if ($offset === 0 || abs($offset) > 14 * 3600) {
            return '';
        }

        $sign = $offset < 0 ? '-' : '+';
        $abs = abs($offset);
        $timezone = sprintf('%s%02d:%02d', $sign, intdiv($abs, 3600), intdiv($abs % 3600, 60));
        return $this->isValidTimezone($timezone) ? $timezone : '';
    }

    private function db() {
        if (!$this->registry || !method_exists($this->registry, 'get')) {
            return null;
        }
        return $this->registry->get('db');
    }

    private function isValidTimezone(string $timezone): bool {
        if ($timezone === '') {
            return false;
        }

        try {
            new \DateTimeZone($timezone);
            return true;
        } catch (\Throwable $e) {
            return false;
        }
    }
}
