from __future__ import annotations

import ctypes
import logging
import os
import platform
import threading
import time
from dataclasses import dataclass
from typing import Optional, Tuple

__all__ = [
    "MetricsTracker",
    "get_global_metrics",
    "record_captcha_solved",
    "record_castle_token_solved",
    "record_account_created",
    "record_failure",
    "record_success",
    "record_begin_verification",
    "record_signup_denied",
    "record_mail_success",
    "record_mail_failure",
    "record_captcha_failure",
    "record_castle_failure",
    "record_error_398",
    "start_metrics_display",
    "stop_metrics_display",
]

LOGGER = logging.getLogger(__name__)


@dataclass
class Metrics:
    accounts_created: int
    captchas_solved: int
    captcha_avg_response_seconds: float
    castle_tokens_solved: int
    captcha_failures: int
    castle_failures: int
    failures: int
    successes: int
    begin_verifications: int
    signup_denied: int
    signup_denied_percentage: float
    mail_success: int
    mail_failure: int
    error_398: int
    cpm: float  
    uptime_seconds: float


class MetricsTracker:

    def __init__(self) -> None:
        self._lock = threading.Lock()
        self._accounts_created = 0
        self._captchas_solved = 0
        self._captcha_total_duration = 0.0
        self._castle_tokens_solved = 0
        self._captcha_failures = 0
        self._castle_failures = 0
        self._failures = 0
        self._successes = 0
        self._begin_verifications = 0
        self._signup_denied = 0
        self._mail_success = 0
        self._mail_failure = 0
        self._error_398 = 0
        self._start_time = time.time()
        self._display_thread: Optional[threading.Thread] = None
        self._display_active = False
        self._update_interval = 2.0
        self._last_logged_signature: Optional[
            Tuple[int, int, int, int, int, int, int, int, int, int, int]
        ] = None

    def record_captcha_solved(self, duration_seconds: Optional[float] = None) -> None:
        with self._lock:
            self._captchas_solved += 1
            if duration_seconds is not None:
                try:
                    duration = float(duration_seconds)
                except (TypeError, ValueError):
                    duration = None
                if duration is not None and duration >= 0:
                    self._captcha_total_duration += duration

    def record_captcha_failure(self) -> None:
        with self._lock:
            self._captcha_failures += 1

    def record_castle_token_solved(self) -> None:
        with self._lock:
            self._castle_tokens_solved += 1

    def record_castle_failure(self) -> None:
        with self._lock:
            self._castle_failures += 1

    def record_account_created(self) -> None:
        with self._lock:
            self._accounts_created += 1

    def record_failure(self) -> None:
        with self._lock:
            self._failures += 1

    def record_success(self) -> None:
        with self._lock:
            self._successes += 1

    def record_begin_verification(self) -> None:
        with self._lock:
            self._begin_verifications += 1

    def record_signup_denied(self) -> None:
        with self._lock:
            self._signup_denied += 1

    def record_mail_success(self) -> None:
        with self._lock:
            self._mail_success += 1

    def record_mail_failure(self) -> None:
        with self._lock:
            self._mail_failure += 1

    def record_error_398(self) -> None:
        with self._lock:
            self._error_398 += 1

    def get_metrics(self) -> Metrics:
        with self._lock:
            uptime = time.time() - self._start_time
            uptime_minutes = uptime / 60.0
            cpm = self._accounts_created / uptime_minutes if uptime_minutes > 0 else 0.0
            captcha_avg = (
                self._captcha_total_duration / self._captchas_solved
                if self._captchas_solved > 0
                else 0.0
            )
            
            signup_denied_percentage = 0.0
            if self._begin_verifications > 0:
                signup_denied_percentage = (self._signup_denied / self._begin_verifications) * 100.0

            return Metrics(
                accounts_created=self._accounts_created,
                captchas_solved=self._captchas_solved,
                captcha_avg_response_seconds=captcha_avg,
                castle_tokens_solved=self._castle_tokens_solved,
                captcha_failures=self._captcha_failures,
                castle_failures=self._castle_failures,
                failures=self._failures,
                successes=self._successes,
                begin_verifications=self._begin_verifications,
                signup_denied=self._signup_denied,
                mail_success=self._mail_success,
                mail_failure=self._mail_failure,
                error_398=self._error_398,
                signup_denied_percentage=signup_denied_percentage,
                cpm=cpm,
                uptime_seconds=uptime,
            )

    def reset(self) -> None:
        with self._lock:
            self._accounts_created = 0
            self._captchas_solved = 0
            self._captcha_total_duration = 0.0
            self._castle_tokens_solved = 0
            self._captcha_failures = 0
            self._castle_failures = 0
            self._failures = 0
            self._successes = 0
            self._begin_verifications = 0
            self._signup_denied = 0
            self._mail_success = 0
            self._mail_failure = 0
            self._error_398 = 0
            self._start_time = time.time()

    def start_display(self, interval: float = 2.0) -> None:
        if self._display_active:
            return

        self._update_interval = max(0.5, interval)
        self._display_active = True
        self._display_thread = threading.Thread(
            target=self._display_loop,
            name="MetricsDisplay",
            daemon=True,
        )
        self._display_thread.start()
        LOGGER.info("Metrics display started (update interval: %.1fs)", self._update_interval)

    def stop_display(self) -> None:
        if not self._display_active:
            return

        self._display_active = False
        if self._display_thread is not None:
            self._display_thread.join(timeout=5.0)
            self._display_thread = None
        LOGGER.info("Metrics display stopped.")

    def _display_loop(self) -> None:
        while self._display_active:
            try:
                metrics = self.get_metrics()
                self._update_terminal_title(metrics)
                self._log_metrics(metrics)
            except Exception:
                LOGGER.debug("Error updating terminal title.", exc_info=True)

            elapsed = 0.0
            while elapsed < self._update_interval and self._display_active:
                time.sleep(0.1)
                elapsed += 0.1

    def _format_uptime(self, metrics: Metrics) -> str:
        hours = int(metrics.uptime_seconds // 3600)
        minutes = int((metrics.uptime_seconds % 3600) // 60)
        seconds = int(metrics.uptime_seconds % 60)

        if hours > 0:
            return f"{hours}h{minutes}m"
        if minutes > 0:
            return f"{minutes}m{seconds}s"
        return f"{seconds}s"

    def _format_metrics_summary(self, metrics: Metrics, *, colored: bool = False) -> str:
        def _c(text: str, color: str) -> str:
            if not colored:
                return text
            reset = "\033[0m"
            return f"{color}{text}{reset}"

        green = "\033[32m"
        cyan = "\033[36m"
        magenta = "\033[35m"
        yellow = "\033[33m"
        red = "\033[31m"
        blue = "\033[34m"

        uptime_str = self._format_uptime(metrics)
        avg_captcha = (
            f"{metrics.captcha_avg_response_seconds:.1f}s"
            if metrics.captchas_solved > 0
            else "n/a"
        )
        return (
            "Metrics | "
            f"{_c(f'CPM: {metrics.cpm:.2f}', cyan)} | "
            f"{_c(f'Accounts: {metrics.accounts_created}', green)} | "
            f"{_c(f'Captchas: {metrics.captchas_solved}', cyan)} | "
            f"{_c(f'Avg response: {avg_captcha}', cyan)} | "
            f"{_c(f'Captcha S/F: {metrics.captchas_solved}/{metrics.captcha_failures}', cyan)} | "
            f"{_c(f'Castle: {metrics.castle_tokens_solved}', magenta)} | "
            f"{_c(f'Castle S/F: {metrics.castle_tokens_solved}/{metrics.castle_failures}', magenta)} | "
            f"{_c(f'Success: {metrics.successes}', green)} | "
            f"{_c(f'Fail: {metrics.failures}', red)} | "
            f"{_c(f'ERROR398: {metrics.error_398}', red)} | "
            f"{_c(f'Mail S/F: {metrics.mail_success}/{metrics.mail_failure}', yellow)} | "
            f"{_c(f'Denied: {metrics.signup_denied_percentage:.1f}%', red)} | "
            f"{_c(f'Uptime: {uptime_str}', blue)}"
        )

    def _update_terminal_title(self, metrics: Metrics) -> None:
        title = "xGen | " + self._format_metrics_summary(metrics, colored=False)[len("Metrics | ") :]
        _set_terminal_title(title)

    def _log_metrics(self, metrics: Metrics) -> None:
        signature = (
            metrics.accounts_created,
            metrics.captchas_solved,
            metrics.castle_tokens_solved,
            metrics.captcha_failures,
            metrics.castle_failures,
            metrics.successes,
            metrics.failures,
            metrics.error_398,
            metrics.signup_denied,
            metrics.mail_success,
            metrics.mail_failure,
        )
        if signature == self._last_logged_signature:
            return

        self._last_logged_signature = signature
        LOGGER.info(self._format_metrics_summary(metrics, colored=True))


_GLOBAL_METRICS: Optional[MetricsTracker] = None
_GLOBAL_METRICS_LOCK = threading.Lock()


def get_global_metrics() -> MetricsTracker:
    global _GLOBAL_METRICS
    with _GLOBAL_METRICS_LOCK:
        if _GLOBAL_METRICS is None:
            _GLOBAL_METRICS = MetricsTracker()
        return _GLOBAL_METRICS


def record_captcha_solved(duration_seconds: Optional[float] = None) -> None:
    get_global_metrics().record_captcha_solved(duration_seconds)


def record_captcha_failure() -> None:
    get_global_metrics().record_captcha_failure()


def record_castle_token_solved() -> None:
    get_global_metrics().record_castle_token_solved()


def record_castle_failure() -> None:
    get_global_metrics().record_castle_failure()


def record_error_398() -> None:
    get_global_metrics().record_error_398()


def record_account_created() -> None:
    get_global_metrics().record_account_created()


def record_failure() -> None:
    get_global_metrics().record_failure()


def record_success() -> None:
    get_global_metrics().record_success()


def record_begin_verification() -> None:
    get_global_metrics().record_begin_verification()


def record_signup_denied() -> None:
    get_global_metrics().record_signup_denied()


def record_mail_success() -> None:
    get_global_metrics().record_mail_success()


def record_mail_failure() -> None:
    get_global_metrics().record_mail_failure()


def start_metrics_display(interval: float = 2.0) -> None:
    get_global_metrics().start_display(interval)


def stop_metrics_display() -> None:
    get_global_metrics().stop_display()


def _set_terminal_title(title: str) -> None:
    system = platform.system()

    try:
        if system == "Windows":
            _set_windows_title(title)
        else:
            _set_unix_title(title)
    except Exception:
        pass


def _set_windows_title(title: str) -> None:
    try:
        ctypes.windll.kernel32.SetConsoleTitleW(title)
    except Exception:
        try:
            safe_title = title.replace('"', '\\"')
            os.system(f'title {safe_title}')
        except Exception:
            pass


def _set_unix_title(title: str) -> None:
    if not os.isatty(1):
        return

    try:
        print(f"\033]0;{title}\007", end="", flush=True)
    except Exception:
        pass
