"""
Email verification service for quick registration and account activation.
Supports 6-digit secure numeric OTPs, activation link tokens, cooldown enforcement, and pluggable senders.
"""

from __future__ import annotations
import abc
import secrets
import time
from typing import Any, Dict, List, Optional, Tuple
from dataclasses import dataclass, field


@dataclass(slots=True)
class VerificationCode:
    """Activation code and token record."""
    email: str
    account_id: str
    code: str
    token: str
    created_at: float
    expires_at: float
    failed_attempts: int = 0
    is_consumed: bool = False

    def is_expired(self, current_time: Optional[float] = None) -> bool:
        now = current_time if current_time is not None else time.time()
        return now > self.expires_at


class EmailSender(abc.ABC):
    """Abstract interface for email dispatching."""
    
    @abc.abstractmethod
    def send_email(
        self,
        to_email: str,
        subject: str,
        body: str,
        html_body: Optional[str] = None,
    ) -> bool:
        pass


class MockEmailSender(EmailSender):
    """In-memory email sender for unit tests and local simulation."""

    def __init__(self) -> None:
        self.sent_messages: List[Dict[str, str]] = []

    def send_email(
        self,
        to_email: str,
        subject: str,
        body: str,
        html_body: Optional[str] = None,
    ) -> bool:
        self.sent_messages.append({
            "to": to_email,
            "subject": subject,
            "body": body,
            "html_body": html_body or "",
            "timestamp": str(time.time()),
        })
        return True


class ConsoleEmailSender(EmailSender):
    """Development console email sender."""

    def send_email(
        self,
        to_email: str,
        subject: str,
        body: str,
        html_body: Optional[str] = None,
    ) -> bool:
        print(f"\n[DEV EMAIL SENDER] To: {to_email} | Subject: {subject}\n{body}\n")
        return True


class EmailVerificationService:
    """Manages email activation OTPs, link tokens, cooldowns, and verification."""

    def __init__(
        self,
        email_sender: EmailSender,
        code_ttl_seconds: int = 900,     # 15 minutes
        cooldown_seconds: int = 60,      # 60 seconds resend cooldown
        max_attempts: int = 5,
    ) -> None:
        self.email_sender = email_sender
        self.code_ttl_seconds = code_ttl_seconds
        self.cooldown_seconds = cooldown_seconds
        self.max_attempts = max_attempts
        self._records: Dict[str, VerificationCode] = {}
        self._last_sent: Dict[str, float] = {}

    def _normalize_email(self, email: str) -> str:
        return email.strip().lower()

    def can_resend(self, email: str) -> Tuple[bool, float]:
        norm_email = self._normalize_email(email)
        last_time = self._last_sent.get(norm_email)
        if last_time is None:
            return True, 0.0

        elapsed = time.time() - last_time
        remaining = self.cooldown_seconds - elapsed
        if remaining > 0:
            return False, remaining
        return True, 0.0

    def send_activation_email(self, email: str, account_id: str) -> VerificationCode:
        """Generate secure OTP and send activation email."""
        norm_email = self._normalize_email(email)
        now = time.time()

        can_send, remaining = self.can_resend(norm_email)
        if not can_send:
            raise ValueError(f"Rate limited: Please wait {int(remaining)} seconds before requesting a new code")

        # Cryptographically secure 6-digit numeric OTP
        code = f"{secrets.randbelow(1_000_000):06d}"
        token = secrets.token_urlsafe(32)
        expires_at = now + self.code_ttl_seconds

        record = VerificationCode(
            email=norm_email,
            account_id=account_id,
            code=code,
            token=token,
            created_at=now,
            expires_at=expires_at,
        )
        self._records[norm_email] = record
        self._last_sent[norm_email] = now

        subject = "[FreeExile] Mã Kích Hoạt Tài Khoản Cổ Võ Hắc Ám"
        body = (
            f"Chào mừng chiến binh lưu đày đến với FreeExile!\n\n"
            f"Mã xác thực kích hoạt tài khoản của bạn là: {code}\n"
            f"Mã này có hiệu lực trong vòng {self.code_ttl_seconds // 60} phút.\n\n"
            f"Hoặc nhấp vào liên kết sau để kích hoạt nhanh:\n"
            f"https://freeexile.io/activate?email={norm_email}&token={token}\n\n"
            f"Nếu bạn không thực hiện yêu cầu này, vui lòng bỏ qua email."
        )

        self.email_sender.send_email(to_email=norm_email, subject=subject, body=body)
        return record

    def get_active_record(self, email: str) -> Optional[VerificationCode]:
        return self._records.get(self._normalize_email(email))

    def verify_code(self, email: str, code_or_token: str) -> Tuple[bool, str]:
        """Verify OTP or activation token with single-use and max retry protection."""
        norm_email = self._normalize_email(email)
        record = self._records.get(norm_email)

        if not record:
            return False, "No active verification request found for this email"

        if record.is_consumed:
            return False, "Verification code has already been used"

        if record.is_expired():
            return False, "Verification code has expired. Please request a new one"

        if record.failed_attempts >= self.max_attempts:
            return False, "Too many failed attempts. Code locked for security"

        cleaned_input = code_or_token.strip()
        is_code_match = secrets.compare_digest(record.code, cleaned_input)
        is_token_match = secrets.compare_digest(record.token, cleaned_input)

        if is_code_match or is_token_match:
            record.is_consumed = True
            return True, "OK"

        record.failed_attempts += 1
        remaining_attempts = max(0, self.max_attempts - record.failed_attempts)
        return False, f"Incorrect code. {remaining_attempts} attempts remaining"
