<?php

namespace App\Libraries;

/**
 * Single source of truth for Egypt (Africa/Cairo) wall-clock time across the booking site.
 * Uses IANA Africa/Cairo; adds +1h during Egypt summer when PHP tzdata is stuck at +02:00.
 */
class EgyptTime
{
    public const TZ_ID = 'Africa/Cairo';

    public static function timezone(): \DateTimeZone
    {
        static $tz = null;

        return $tz ??= new \DateTimeZone(self::TZ_ID);
    }

    /** Ensure date(), strtotime(), and CI time helpers use Egypt. */
    public static function ensurePhpDefault(): void
    {
        date_default_timezone_set(self::TZ_ID);
    }

    /**
     * @param string $time "now", "2026-05-21 09:26", "+1 day", etc.
     */
    public static function parse(string $time = 'now'): \DateTime
    {
        $dt = new \DateTime($time, self::timezone());

        return $time === 'now' ? self::correctStaleTzdata($dt) : $dt;
    }

    public static function nowSql(): string
    {
        return self::parse('now')->format('Y-m-d H:i:s');
    }

    /**
     * Clock sources for hold_debug / ops (detect stale tzdata vs wrong server clock).
     *
     * @return array<string, string|int|bool>
     */
    public static function clockDiagnostics(): array
    {
        self::ensurePhpDefault();
        $cairo = self::parse('now');
        $utc = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
        $rawCairo = new \DateTimeImmutable('now', self::timezone());
        $mysqlNow = '';
        try {
            self::syncMysqlTimezone();
            $row = \Config\Database::connect()->query('SELECT NOW() AS t')->getRowArray();
            $mysqlNow = (string) ($row['t'] ?? '');
        } catch (\Throwable $e) {
            $mysqlNow = '';
        }

        return [
            'egypt_now' => $cairo->format('Y-m-d H:i:s'),
            'cairo_offset' => $cairo->format('P'),
            'php_default_tz' => date_default_timezone_get(),
            'raw_cairo_before_correction' => $rawCairo->format('Y-m-d H:i:s P'),
            'tzdata_dst_correction_applied' => $rawCairo->format('Y-m-d H:i:s') !== $cairo->format('Y-m-d H:i:s'),
            'utc_now' => $utc->format('Y-m-d H:i:s'),
            'mysql_now' => $mysqlNow,
            'unix_ts' => time(),
        ];
    }

    /**
     * Older PHP/OS tzdata keeps Africa/Cairo at +02:00 year-round; Egypt uses +03:00
     * from the last Friday in April through the last Thursday in October (since 2023).
     */
    public static function correctStaleTzdata(\DateTime $dt): \DateTime
    {
        if (! self::isInEgyptDstCalendarWindow($dt)) {
            return $dt;
        }

        if (self::timezone()->getOffset($dt) !== 7200) {
            return $dt;
        }

        $fixed = clone $dt;
        $fixed->modify('+1 hour');

        return $fixed;
    }

    /** @param \DateTime|\DateTimeImmutable $dt */
    public static function isInEgyptDstCalendarWindow($dt): bool
    {
        $year = (int) $dt->format('Y');
        $tz = self::timezone();
        $start = new \DateTime("last friday of April {$year} 00:00:00", $tz);
        $end = new \DateTime("last thursday of October {$year} 23:59:59", $tz);
        $instant = $dt instanceof \DateTimeImmutable ? \DateTime::createFromImmutable($dt) : clone $dt;

        return $instant >= $start && $instant <= $end;
    }

    public static function today(): string
    {
        return self::parse('now')->format('Y-m-d');
    }

    /**
     * Normalize a calendar date or datetime string to Y-m-d in Egypt.
     */
    public static function toDateString(string $dateOrDatetime): string
    {
        return self::parse($dateOrDatetime)->format('Y-m-d');
    }

    /**
     * Admin form / flatpickr value: store the wall-clock time the user entered (Cairo).
     */
    public static function wallClock(?string $value): ?string
    {
        if ($value === null || trim($value) === '') {
            return null;
        }

        $value = trim($value);
        if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value)) {
            $value .= ' 00:00:00';
        } elseif (preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $value)) {
            $value .= ':00';
        }

        try {
            return self::parse($value)->format('Y-m-d H:i:s');
        } catch (\Exception $e) {
            return $value;
        }
    }

    public static function addMinutesSql(int $minutes, string $from = 'now'): string
    {
        $copy = clone self::parse($from);
        $copy->modify('+' . $minutes . ' minutes');

        return $copy->format('Y-m-d H:i:s');
    }

    /** MySQL session offset e.g. +02:00 or +03:00 when DST applies. */
    public static function mysqlOffset(): string
    {
        return self::parse('now')->format('P');
    }

    public static function syncMysqlTimezone(): void
    {
        try {
            $db = \Config\Database::connect();
            $offset = self::mysqlOffset();
            $db->query("SET time_zone = '{$offset}'");
        } catch (\Throwable $e) {
            // ignore
        }
    }
}
