<?php

namespace App\Services;

use App\Models\PreferencesModel;
use App\Models\SettingsModel;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\I18n\Time;

class TimeService
{
    private string $defaultUserTimezone = 'America/New_York';
    private string $serverTimezone = 'UTC';

    // Cached per-request user timezone
    private ?string $cachedUserTz = null;

    /**
     * Prime detection cache from the Request (optional call, lazy by default).
     */
    public function primeFromRequest(?RequestInterface $request = null): void
    {
        $this->cachedUserTz = $this->detectUserTimezone($request);
    }

    /**
     * Get the resolved user timezone (detect lazily if needed).
     */
    public function userTimezone(?RequestInterface $request = null): string
    {
        if ($this->cachedUserTz !== null) {
            return $this->cachedUserTz;
        }

        $this->cachedUserTz = $this->detectUserTimezone($request);
        return $this->cachedUserTz;
    }

    /**
     * Core detection logic (headers > user pref > settings > school config > default).
     */
    public function detectUserTimezone(?RequestInterface $request = null): string
    {
        $request = $request ?: service('request');

        // 1) Explicit client header
        try {
            $tzHeader = $request->getHeaderLine('X-Timezone')
                ?: $request->getHeaderLine('Timezone')
                ?: $request->getHeaderLine('Accept-Timezone')
                ?: null;

            if ($tzHeader && in_array($tzHeader, timezone_identifiers_list(), true)) {
                return $tzHeader;
            }
        } catch (\Throwable $e) {
            // ignore
        }

        // 2) User preference (if logged-in)
        try {
            $userId = (int) (session('user_id') ?: 0);
            if ($userId > 0) {
                // Do not select a specific column; some DBs may not have it yet
                $pref = (new PreferencesModel())
                    ->where('user_id', $userId)
                    ->first();
                $tz = $pref['timezone'] ?? null;
                if ($tz && in_array($tz, timezone_identifiers_list(), true)) {
                    return $tz;
                }
            }
        } catch (\Throwable $e) {
            // ignore
        }

        // 3) Global settings timezone (if available)
        try {
            $settings = (new SettingsModel())->getSettings();
            $tz = $settings['timezone'] ?? null;
            if ($tz && in_array($tz, timezone_identifiers_list(), true)) {
                return $tz;
            }
        } catch (\Throwable $e) {
            // ignore
        }

        // 4) School attendance timezone (if defined)
        try {
            $tz = (string) (config('School')->attendance['timezone'] ?? '');
            if ($tz && in_array($tz, timezone_identifiers_list(), true)) {
                return $tz;
            }
        } catch (\Throwable $e) {
            // ignore
        }

        // 5) Default
        return $this->defaultUserTimezone;
    }

    /**
     * Server timezone (for storage/UTC operations).
     */
    public function serverTimezone(): string
    {
        return $this->serverTimezone;
    }

    /**
     * Current time in user timezone as CI Time.
     */
    public function nowLocal(?string $tz = null): Time
    {
        $tz = $tz ?: $this->userTimezone();
        return Time::now($tz);
    }

    /**
     * Current time in UTC as CI Time.
     */
    public function nowUTC(): Time
    {
        return Time::now($this->serverTimezone);
    }

    /**
     * Convert any supported input to UTC string (Y-m-d H:i:s by default).
     *
     * @param string|Time|\DateTimeInterface|null $value
     */
    public function toUTC($value, ?string $fromTz = null, string $format = 'Y-m-d H:i:s'): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        $fromTz = $fromTz ?: $this->userTimezone();

        try {
            if ($value instanceof Time) {
                return $value->setTimezone($this->serverTimezone)->toDateTimeString();
            }
            if ($value instanceof \DateTimeInterface) {
                return Time::createFromInstance($value)
                    ->setTimezone($this->serverTimezone)
                    ->format($format);
            }

            // assume string
            return Time::parse((string)$value, $fromTz)
                ->setTimezone($this->serverTimezone)
                ->format($format);
        } catch (\Throwable $e) {
            return null;
        }
    }

    /**
     * Convert UTC (or provided timezone) to user-local string.
     *
     * @param string|Time|\DateTimeInterface|null $value
     */
    public function toLocal($value, ?string $sourceTz = null, ?string $targetTz = null, string $format = 'Y-m-d H:i:s'): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        $targetTz = $targetTz ?: $this->userTimezone();
        if ($sourceTz === null && $this->isDateOnlyString($value)) {
            // Date-only strings should not shift across timezones.
            $sourceTz = $targetTz;
        } else {
            $sourceTz = $sourceTz ?: $this->serverTimezone;
        }

        try {
            if ($value instanceof Time) {
                return $value->setTimezone($targetTz)->format($format);
            }
            if ($value instanceof \DateTimeInterface) {
                return Time::createFromInstance($value)
                    ->setTimezone($targetTz)
                    ->format($format);
            }

            // assume string
            return Time::parse((string)$value, $sourceTz)
                ->setTimezone($targetTz)
                ->format($format);
        } catch (\Throwable $e) {
            return null;
        }
    }

    /**
     * Convenience formatter for user-local.
     */
    public function formatLocal($value, string $format = 'Y-m-d H:i', ?string $sourceTz = null): string
    {
        return (string) ($this->toLocal($value, $sourceTz, null, $format) ?? '');
    }

    /**
     * Convenience formatter for UTC.
     */
    public function formatUTC($value, string $format = 'Y-m-d H:i:s', ?string $fromTz = null): string
    {
        return (string) ($this->toUTC($value, $fromTz, $format) ?? '');
    }

    private function isDateOnlyString($value): bool
    {
        if (!is_string($value)) {
            return false;
        }

        $value = trim($value);
        return (bool) preg_match('/^\d{4}-\d{2}-\d{2}$/', $value);
    }
}
