"""
ActivityTimezoneMixin — timezone detection, validation, and normalization.

Provides timezone verification and mismatch handling for
:class:`~ws_package.controllers.activity_controller.ActivityController`.
All methods assume ``self`` is an ``ActivityController`` instance and have access
to all attributes defined in ``ActivityController.__init__``.

Thread-safety:
    ``timecheck()`` and ``checkUserTimeZone()`` are async coroutines and must be
    awaited from the monitoring loop.  Synchronous helpers (``get_system_timezone()``,
    ``_normalize_timezone()``) are thread-safe as they perform no shared-state writes
    beyond ``self.window_timezone`` which is only written from the monitoring loop.
"""
from __future__ import annotations

import datetime
import platform
from typing import Optional

import requests
from tzlocal import get_localzone, reload_localzone

from ws_package.log.writer import LogWriter
from ws_package.configs import config
from ws_package.entities.user_entity import UserEntity
from ws_package.utils.api_log_policy import log_api_response_summary, log_testing_request_headers

logger = LogWriter.file(__name__)


class ActivityTimezoneMixin:
    """Timezone detection, comparison, registry lookup, and mismatch handling for ActivityController."""

    def _timezone_checks_enabled(self, user_entity: Optional[UserEntity] = None) -> bool:
        """Return True only when timezone_preference=0 (checks on). 1 = no checks.

        Prefers the live org setting on ActivityController (refreshed each interval),
        then falls back to UserEntity.
        """
        if getattr(self, "timezone_preference", None) is not None:
            preference = self.timezone_preference
        else:
            user = user_entity or getattr(self, "user_entity", None)
            preference = getattr(user, "timezone_preference", 1)
        try:
            enabled = int(preference) == 0
        except (TypeError, ValueError):
            # Missing/malformed defaults to no checks (same as preference=1).
            enabled = False
        logger.debug(
            "[TimezonePref] preference=%s checks_enabled=%s (source=org_or_user)",
            preference,
            enabled,
        )
        return enabled

    def get_os_type(self) -> str:
        """Return the lower-case OS type string from the activity manager (e.g. ``'windows'``)."""
        return self.activity_manager.get_os_type()

    def get_today_weekday(self) -> str:
        """Return the current weekday name via the timezone manager."""
        return self.timezone_manager.get_today_weekday()

    def get_weekday(self, date_str: str) -> str:
        """Return the weekday name for the given date string via the timezone manager."""
        return self.timezone_manager.get_weekday(date_str)

    def _normalize_timezone(self, timezone_str: str) -> str:
        """
        Normalize timezone string - convert deprecated names to modern equivalents.
        Specifically converts Asia/Calcutta to Asia/Kolkata.

        Args:
            timezone_str: Timezone string to normalize

        Returns:
            Normalized timezone string
        """
        if not timezone_str:
            return timezone_str

        # Convert Asia/Calcutta to Asia/Kolkata (deprecated to modern)
        if timezone_str == "Asia/Calcutta":
            logger.info("Converting timezone from Asia/Calcutta to Asia/Kolkata in payload")
            return "Asia/Kolkata"

        return timezone_str

    def get_system_timezone(self):
        """Return the system's local timezone with platform-specific handling.

        On Linux uses ``timedatectl``; on macOS reloads via ``tzlocal``; on Windows
        reads from the registry with ``get_localzone()`` as primary and registry as
        fallback.  Falls back to ``"UTC"`` if all methods fail on Windows.

        Returns:
            A timezone object or string representing the local timezone.
        """
        if platform.system() == "Linux":
            import subprocess
            output = subprocess.check_output(
                ["timedatectl", "show", "-p", "Timezone"]
            ).decode().strip()
            return output.split("=")[1]

        elif self.get_os_type_caps() == 'Mac':
            tz = reload_localzone()
            return tz
        else:
            # Windows: Add error handling for timezone changes
            if platform.system() == "Windows":
                try:
                    return get_localzone()
                except Exception as e:
                    logger.error("Error getting local timezone on Windows (timezone may be changing): %s", e)
                    # Send timezone mismatch message to Electron when primary method fails
                    self._send_timezone_mismatch_to_electron(f"Error getting local timezone: {e}")
                    # Try to get from registry as fallback
                    try:
                        registry_tz = self.current_registry_timezone()
                        if registry_tz:
                            return registry_tz
                    except Exception as reg_e:
                        logger.error("Error getting timezone from registry: %s", reg_e)
                    # Ultimate fallback
                    return "UTC"
            else:
                return get_localzone()

    async def checkUserTimeZone(
        self, local_timezone, sd_token, org_id, apply_failure_actions: bool = True
    ):
        """Check if user's local timezone matches the server timezone via the WS API."""
        if not self._timezone_checks_enabled():
            return True

        url = config('application_check_timezone_url', 'Application')

        headers = {
            "Authorization": f"Bearer {sd_token}",
            "Content-Type": "application/json",
            "OrgID": org_id,
        }
        from ws_package.utils.desktop_key import apply_desktop_key
        headers = apply_desktop_key(headers)
        if str(local_timezone) == "Asia/Calcutta":
            logger.info("Change Timezone to Asia/Kolkata from Asia/Calcutta")
            local_timezone = "Asia/Kolkata"

        # Define the body (payload)
        body = {
            "dst": True,
            "timezone": local_timezone,
        }
        try:
            log_testing_request_headers(logger, "POST", url, headers, body)
            response = requests.post(url, params=body, headers=headers)
            log_api_response_summary(
                logger,
                url,
                response.status_code,
                response.text,
            )
            if response.status_code == 200:
                api_response = response.json()
                response = api_response.get("response")
                api_datetime_str = response.get("data_withoutDST")
                api_datetime = datetime.datetime.strptime(api_datetime_str, "%Y-%m-%d %H:%M:%S")
                server_datetime_seconds = api_datetime.timestamp()
                current_datetime_seconds = datetime.datetime.now().timestamp()
                time_difference2 = abs(current_datetime_seconds - server_datetime_seconds)
                if time_difference2 <= 60:
                    logger.info("Time match")
                    return True  # Indicates timezone match
                else:
                    logger.error("Time difference exceeds the acceptable range.")
                    if apply_failure_actions:
                        self.force_stop = True
                        self._timezone_mismatch = True  # Set dedicated timezone mismatch flag
                    return False  # Indicates mismatch
            elif response.status_code == 1001:
                logger.error("Invalid SD Token or Org ID")
                return True
            else:
                logger.warning("Cannot reach WS server for timezone match, proceeding anyway. Status Code: %s", response.status_code)
                return True
        except requests.exceptions.RequestException as e:
            logger.warning("Cannot reach WS server for timezone match, proceeding anyway: %s", e)
            return True
        except Exception as e:
            logger.warning("Cannot reach WS server for timezone match, proceeding anyway: %s", e)
            return True

    def _send_timezone_mismatch_to_electron(self, error_details: str = None) -> None:
        """Send a timezone-mismatch notification to the Electron frontend (Windows-only).

        Args:
            error_details: Optional extra context appended to the notification payload.
        """
        if platform.system() != "Windows" or not self._timezone_checks_enabled():
            return

        try:
            error_data = {
                'title': 'Timezone Mismatch Detected',
                'message': "Your system timezone does not match the organization's configured timezone.\nPlease update your system time settings to continue tracking",
                'timestamp': datetime.datetime.now().isoformat()
            }
            if error_details:
                error_data['details'] = error_details
            if self._send_to_electron_if_connected('timezone_error', error_data):
                logger.info("Sent timezone mismatch error to Electron: %s", error_data)
        except Exception as e:
            logger.error("Failed to send timezone error to Electron: %s", e, exc_info=True)

    def current_registry_timezone(self):
        """Get the timezone name from the Windows registry and cache it in ``self.window_timezone``.

        On non-Windows systems falls back to ``str(get_localzone())``.

        Returns:
            The timezone key name string, or ``None`` if the registry key is unavailable.
        """
        try:
            if platform.system() != "Windows":
                # For non-Windows, store string form of local tz for consistency
                try:
                    self.window_timezone = str(get_localzone())
                    return self.window_timezone
                except Exception as e:
                    logger.error("Error getting local timezone on non-Windows: %s", e)
                    self.window_timezone = None
                    return None

            import winreg
            registry_path = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation"
            with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, registry_path) as key:
                tz_name, _ = winreg.QueryValueEx(key, "TimeZoneKeyName")
                self.window_timezone = tz_name
                return tz_name
        except FileNotFoundError as e:
            logger.error("Registry key not found: %s", e)
            self.window_timezone = None
            # Send timezone mismatch message to Electron on Windows
            if platform.system() == "Windows":
                self._send_timezone_mismatch_to_electron(f"Registry key not found: {e}")
            return None
        except Exception as e:
            logger.error("Error reading Windows timezone: %s", e)
            self.window_timezone = None
            # Send timezone mismatch message to Electron on Windows
            if platform.system() == "Windows":
                self._send_timezone_mismatch_to_electron(f"Error reading registry: {e}")
            return None

    async def timecheck(self, user_entity: UserEntity, apply_failure_actions: bool = True):
        """
        Verify that the system timezone matches the organization's configured timezone.
        Returns True on match or when verification cannot run (missing org/local timezone).
        Returns False only on a confirmed mismatch.

        When ``apply_failure_actions`` is False, mismatch is reported without setting
        ``force_stop``, ``_timezone_mismatch``, or notifying Electron
        (activity loop owns those actions after a failed check).
        """
        if not self._timezone_checks_enabled(user_entity):
            if self._timezone_mismatch:
                logger.info(
                    "Ignoring stale timezone mismatch because timezone_preference=1 (checks off)"
                )
                self._timezone_mismatch = False
                if self._time_error_type == "timezone_error":
                    self._time_error_type = None
            return True

        if platform.system() == "Windows":
            try:
                self.local_timezone = self.get_system_timezone()
            except Exception as e:
                logger.warning(
                    "Error getting system timezone on Windows (may be changing): %s — trying registry",
                    e,
                )
                try:
                    self.local_timezone = self.current_registry_timezone()
                except Exception as reg_e:
                    logger.warning(
                        "Cannot read system timezone from registry — skipping mismatch check: %s",
                        reg_e,
                    )
                    return True
                if self.local_timezone is None:
                    logger.warning(
                        "System and registry timezone unavailable — skipping mismatch check"
                    )
                    return True
        else:
            try:
                self.local_timezone = self.get_system_timezone()
            except Exception as e:
                logger.warning(
                    "Cannot read system timezone — skipping mismatch check: %s", e
                )
                return True

        org_tz = self.org_api_timezone
        if org_tz is None or (isinstance(org_tz, str) and not str(org_tz).strip()):
            logger.warning(
                "Org timezone not available — skipping timezone mismatch check (local=%s)",
                self.local_timezone,
            )
            return True

        if self.local_timezone is None or (
            isinstance(self.local_timezone, str) and not str(self.local_timezone).strip()
        ):
            logger.warning("Local timezone not available — skipping timezone mismatch check")
            return True

        logger.info("Local Timezone of recent interval: %s", self.local_timezone)
        logger.info("Server Timezone of recent interval: %s", self.org_api_timezone)

        if str(self.local_timezone) == "Asia/Calcutta":
            logger.info("Change Timezone to Asia/Kolkata from Asia/Calcutta")
            self.local_timezone = "Asia/Kolkata"

        is_time_match = await self.checkUserTimeZone(
            str(self.local_timezone),
            user_entity.sd_token,
            str(user_entity.org_id),
            apply_failure_actions=apply_failure_actions,
        )

        if platform.system() == "Windows":
            try:
                self.current_registry_timezone()
            except Exception as e:
                logger.warning(
                    "Error reading Windows registry timezone — skipping registry comparison: %s",
                    e,
                )

            if self.ws_timezone is None:
                self.ws_timezone = self.window_timezone

            if self.window_timezone is not None and self.ws_timezone is not None:
                logger.info(
                    "Current Window Timezone ->> %s, server timezone ->> %s",
                    self.window_timezone,
                    self.ws_timezone,
                )
                logger.info(
                    "User Timezone ->> %s, Org API timezone ->> %s",
                    self.local_timezone,
                    self.org_api_timezone,
                )
                if self.window_timezone != self.ws_timezone:
                    logger.info("Timezone mismatch detected on Windows (registry vs server)")
                    mismatch_details = (
                        f"Window timezone: {self.window_timezone}, "
                        f"Server timezone: {self.ws_timezone}, "
                        f"Org API timezone: {self.org_api_timezone}, "
                        f"Time match: {is_time_match}"
                    )
                    if apply_failure_actions:
                        self._send_timezone_mismatch_to_electron(mismatch_details)
                        self.force_stop = True
                        self._timezone_mismatch = True
                    return False
            else:
                logger.warning(
                    "Windows registry/server timezone unavailable — skipping registry comparison "
                    "(local=%s, org=%s)",
                    self.local_timezone,
                    self.org_api_timezone,
                )

        if str(self.local_timezone) != str(self.org_api_timezone):
            logger.info(
                "Timezone mismatch detected: local=%s, org=%s",
                self.local_timezone,
                self.org_api_timezone,
            )
            if apply_failure_actions:
                if platform.system() == "Windows":
                    mismatch_details = (
                        f"Local timezone: {self.local_timezone}, "
                        f"Org API timezone: {self.org_api_timezone}, "
                        f"Time match: {is_time_match}"
                    )
                    self._send_timezone_mismatch_to_electron(mismatch_details)
                self.force_stop = True
                self._timezone_mismatch = True
            return False

        if not is_time_match:
            logger.info("Time mismatch detected from server")
            if apply_failure_actions:
                self.force_stop = True
                self._timezone_mismatch = True
            return False

        return True

    async def timecheck_with_recheck(
        self, user_entity: UserEntity, stop_event=None
    ) -> bool:
        """
        Run ``timecheck`` once and return immediately.

        On mismatch the activity loop sends the timezone error popup and stops
        tracking — no delayed recheck (previously waited 2.5 minutes).

        Returns True when time/timezone is OK (or verification is skipped).
        Returns False when the check reports a mismatch.
        """
        if not self._timezone_checks_enabled(user_entity):
            return True

        ok = await self.timecheck(user_entity, apply_failure_actions=False)
        if not ok:
            logger.warning(
                "Time/timezone mismatch confirmed — failing immediately (no recheck delay)"
            )
        return ok

    def set_local_timezone(self, user_entity):
        """Set local_timezone from user_entity, fallback to system timezone if not available."""
        self.timezone_manager.set_local_timezone(user_entity)
