<?php
declare(strict_types=1);

namespace App\Services;

use App\Core\Auth;
use App\Core\Database;
use App\Models\User;
use DateTimeImmutable;
use DateTimeZone;
use Throwable;

/**
 * Company clock. Admin picks one zone; greetings, due dates, and "today" use it.
 */
final class TimezoneService
{
    public const DEFAULT = 'America/New_York';

    /** @var array<string, string> */
    private const ZONES = [
        'Pacific/Honolulu' => 'Hawaii',
        'America/Anchorage' => 'Alaska',
        'America/Los_Angeles' => 'Pacific Time — Los Angeles',
        'America/Phoenix' => 'Arizona',
        'America/Denver' => 'Mountain Time — Denver',
        'America/Chicago' => 'Central Time — Chicago',
        'America/New_York' => 'Eastern Time — New York',
        'America/Toronto' => 'Toronto',
        'America/Mexico_City' => 'Mexico City',
        'America/Sao_Paulo' => 'São Paulo',
        'UTC' => 'UTC',
        'Europe/London' => 'London',
        'Europe/Paris' => 'Paris',
        'Europe/Berlin' => 'Berlin',
        'Europe/Amsterdam' => 'Amsterdam',
        'Europe/Madrid' => 'Madrid',
        'Africa/Johannesburg' => 'Johannesburg',
        'Asia/Dubai' => 'Dubai',
        'Asia/Kolkata' => 'India — Kolkata',
        'Asia/Singapore' => 'Singapore',
        'Asia/Shanghai' => 'Shanghai',
        'Asia/Tokyo' => 'Tokyo',
        'Australia/Perth' => 'Perth',
        'Australia/Sydney' => 'Sydney',
        'Pacific/Auckland' => 'Auckland',
    ];

    public static function isValid(string $tz): bool
    {
        return in_array($tz, timezone_identifiers_list(), true);
    }

    public static function isAllowed(string $tz): bool
    {
        return isset(self::ZONES[$tz]) && self::isValid($tz);
    }

    public static function normalize(?string $tz): string
    {
        $tz = trim((string) $tz);
        if ($tz !== '' && self::isValid($tz)) {
            return $tz;
        }
        $fallback = (string) config('app.timezone', self::DEFAULT);
        return self::isValid($fallback) ? $fallback : 'UTC';
    }

    /** @return array<string, string> */
    public static function options(?string $current = null): array
    {
        $out = [];
        foreach (self::ZONES as $id => $label) {
            if (!self::isValid($id)) {
                continue;
            }
            $out[$id] = $label . ' (UTC' . self::offset($id) . ')';
        }
        $current = trim((string) $current);
        if ($current !== '' && self::isValid($current) && !isset($out[$current])) {
            $out[$current] = str_replace('_', ' ', $current) . ' (UTC' . self::offset($current) . ')';
        }
        return $out;
    }

    public static function apply(?string $tz): void
    {
        $iana = self::normalize($tz);
        date_default_timezone_set($iana);
        try {
            $offset = self::offset($iana);
            if (preg_match('/^[+-]\d{2}:\d{2}$/', $offset)) {
                Database::instance()->pdo()->exec("SET time_zone = '" . $offset . "'");
            }
        } catch (Throwable $e) {
            // PHP clock still applies if MySQL rejects the offset.
        }
    }

    public static function current(): string
    {
        $tenant = Auth::instance()->tenant();
        if (is_array($tenant) && isset($tenant['timezone']) && (string) $tenant['timezone'] !== '') {
            return self::normalize((string) $tenant['timezone']);
        }
        return self::normalize((string) config('app.timezone', self::DEFAULT));
    }

    public static function save(string $tz): void
    {
        $tz = self::normalize($tz);
        $auth = Auth::instance();
        $tid = $auth->tenantId();
        if (!$tid) {
            return;
        }
        Database::instance()->query(
            'UPDATE tenants SET timezone = :tz WHERE id = :tid',
            ['tz' => $tz, 'tid' => $tid]
        );
        $userId = (int) $auth->id();
        $tenant = User::tenantById($userId, $tid);
        if ($tenant) {
            $auth->setTenant($tenant, $auth->roles());
        }
        self::apply($tz);
    }

    private static function offset(string $tz): string
    {
        return (new DateTimeImmutable('now', new DateTimeZone($tz)))->format('P');
    }
}
