"""Website liveness and health check operator."""

import logging
import ssl
import time
from dataclasses import dataclass
from typing import Optional
from urllib.parse import urlparse

import httpx
import tldextract

from .compliance import ComplianceManager

logger = logging.getLogger(__name__)

DEFAULT_HEADERS = {
    "User-Agent": "FoundryMatch/1.0 Bot",
}


@dataclass
class LivenessResult:
    """Result of website liveness check."""

    is_live: bool
    status_code: Optional[int] = None
    final_url: Optional[str] = None  # After redirects
    final_host: Optional[str] = None
    final_etld1: Optional[str] = None
    redirect_count: int = 0
    has_ssl: bool = False
    tls_valid: Optional[bool] = None
    tls_error: Optional[str] = None
    compliance_reason: Optional[str] = None
    error: Optional[str] = None
    response_time_ms: Optional[float] = None


class WebsiteLivenessOperator:
    """Check if a website is live and healthy."""

    def __init__(
        self,
        timeout: float = 5.0,
        max_redirects: int = 5,
        compliance: Optional[ComplianceManager] = None,
        compliance_wait_timeout: float = 5.0,
    ):
        self.timeout = timeout
        self.max_redirects = max_redirects
        self.compliance = compliance
        self.compliance_wait_timeout = compliance_wait_timeout

    def check(self, domain: str) -> LivenessResult:
        """
        Check if website is live.

        Args:
            domain: Domain to check (e.g., 'example.com')

        Returns:
            LivenessResult with status information
        """
        if not domain:
            return LivenessResult(is_live=False, error="empty_domain")

        # Ensure we have a proper URL
        if not domain.startswith(("http://", "https://")):
            # Try HTTPS first
            urls_to_try = [f"https://{domain}", f"http://{domain}"]
        else:
            urls_to_try = [domain]

        last_result: Optional[LivenessResult] = None
        for url in urls_to_try:
            result = self._check_url(url)
            if result.is_live:
                return result
            last_result = result

        # If none worked, return last result (even if None)
        return last_result or LivenessResult(is_live=False, error="unreachable")

    def _check_url(self, url: str) -> LivenessResult:
        """Check a specific URL."""
        parsed = urlparse(url)
        has_ssl = parsed.scheme == "https"

        compliance_reason: Optional[str] = None
        acquired = False

        if self.compliance:
            policy = self.compliance.check_allowed(url)
            if not policy.allowed:
                compliance_reason = policy.reason or "denied"
                return LivenessResult(
                    is_live=False,
                    has_ssl=has_ssl,
                    final_url=url,
                    final_host=parsed.netloc,
                    final_etld1=self._extract_etld1(parsed.netloc),
                    compliance_reason=compliance_reason,
                    error=f"compliance_denied:{compliance_reason}",
                )

            if not self.compliance.wait_if_needed(
                url, max_wait=self.compliance_wait_timeout
            ):
                compliance_reason = "rate_limited"
                return LivenessResult(
                    is_live=False,
                    has_ssl=has_ssl,
                    final_url=url,
                    final_host=parsed.netloc,
                    final_etld1=self._extract_etld1(parsed.netloc),
                    compliance_reason=compliance_reason,
                    error="rate_limited",
                )
            acquired = True

        headers = self.compliance.get_headers() if self.compliance else DEFAULT_HEADERS

        try:
            response, elapsed_ms, tls_valid, tls_error = self._perform_request(
                url, headers, has_ssl
            )

            redirect_count = len(response.history)
            final_url = str(response.url)
            final_host = urlparse(final_url).netloc or parsed.netloc
            final_etld1 = self._extract_etld1(final_host)
            final_has_ssl = final_url.startswith("https://")

            status_code = response.status_code
            is_reachable = 200 <= status_code < 400 or status_code in (401, 403)

            return LivenessResult(
                is_live=is_reachable,
                status_code=status_code,
                final_url=final_url,
                final_host=final_host,
                final_etld1=final_etld1,
                redirect_count=redirect_count,
                has_ssl=final_has_ssl,
                tls_valid=tls_valid if final_has_ssl else False,
                tls_error=tls_error,
                compliance_reason=compliance_reason,
                response_time_ms=round(elapsed_ms, 2)
                if elapsed_ms is not None
                else None,
            )

        except httpx.TimeoutException:
            return LivenessResult(
                is_live=False,
                has_ssl=has_ssl,
                final_url=url,
                final_host=parsed.netloc,
                final_etld1=self._extract_etld1(parsed.netloc),
                compliance_reason=compliance_reason,
                error="timeout",
            )
        except httpx.TooManyRedirects:
            return LivenessResult(
                is_live=False,
                has_ssl=has_ssl,
                final_url=url,
                final_host=parsed.netloc,
                final_etld1=self._extract_etld1(parsed.netloc),
                compliance_reason=compliance_reason,
                error="too_many_redirects",
            )
        except httpx.HTTPError as e:
            return LivenessResult(
                is_live=False,
                has_ssl=has_ssl,
                final_url=url,
                final_host=parsed.netloc,
                final_etld1=self._extract_etld1(parsed.netloc),
                compliance_reason=compliance_reason,
                error=f"http_error:{type(e).__name__}",
            )
        except Exception as e:
            logger.warning(f"Unexpected error checking {url}: {e}")
            return LivenessResult(
                is_live=False,
                has_ssl=has_ssl,
                final_url=url,
                final_host=parsed.netloc,
                final_etld1=self._extract_etld1(parsed.netloc),
                compliance_reason=compliance_reason,
                error=f"unexpected:{type(e).__name__}",
            )
        finally:
            if self.compliance and acquired:
                self.compliance.release(url)

    def _perform_request(
        self,
        url: str,
        headers: dict,
        has_ssl: bool,
    ) -> tuple[httpx.Response, Optional[float], Optional[bool], Optional[str]]:
        """
        Execute HTTP request with optional TLS verification handling.

        Returns:
            response, elapsed_ms, tls_valid, tls_error
        """
        tls_valid: Optional[bool] = None
        tls_error: Optional[str] = None

        # Attempt with verification when using HTTPS to record TLS health.
        if has_ssl:
            try:
                with httpx.Client(
                    timeout=self.timeout,
                    follow_redirects=True,
                    max_redirects=self.max_redirects,
                    verify=True,
                ) as client:
                    start = time.time()
                    response = client.get(url, headers=headers)
                    elapsed_ms = (time.time() - start) * 1000
                tls_valid = True
                return response, elapsed_ms, tls_valid, tls_error
            except httpx.HTTPError as exc:
                cause = getattr(exc, "__cause__", None)
                if isinstance(cause, ssl.SSLCertVerificationError) or isinstance(
                    cause, ssl.SSLError
                ):
                    tls_valid = False
                    tls_error = "ssl_cert_invalid"
                else:
                    raise

        # Either HTTP or HTTPS with invalid certs: retry without verification.
        with httpx.Client(
            timeout=self.timeout,
            follow_redirects=True,
            max_redirects=self.max_redirects,
            verify=False,
        ) as client:
            start = time.time()
            response = client.get(url, headers=headers)
            elapsed_ms = (time.time() - start) * 1000

        return response, elapsed_ms, tls_valid, tls_error

    @staticmethod
    def _extract_etld1(host: str) -> Optional[str]:
        """Extract eTLD+1 from host."""
        if not host:
            return None

        extracted = tldextract.extract(host)
        if not extracted.suffix:
            return None
        return f"{extracted.domain}.{extracted.suffix}"
