"""Kawaa Email Verification API client."""

import time
from typing import Any, Dict, List, Optional, Union

import httpx

from .exceptions import (
    JobNotCompleteError,
    KawaaError,
    RateLimitError,
    TimeoutError,
    raise_for_status,
)
from .models import (
    AccountInfo,
    BatchJobResult,
    DownloadResult,
    FindResult,
    JobStatus,
    JobStatusResult,
    VerificationResult,
)

DEFAULT_BASE_URL = "https://api.kawaa.com"
DEFAULT_TIMEOUT = 30.0
DEFAULT_MAX_RETRIES = 3
DEFAULT_POLL_INTERVAL = 2.0
DEFAULT_POLL_TIMEOUT = 300.0


class Kawaa:
    """
    Kawaa Email Verification API client.

    Example:
        >>> from kawaa import Kawaa
        >>> client = Kawaa("your-api-key")
        >>> result = client.verify("test@example.com")
        >>> print(result.status, result.score)
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
        max_retries: int = DEFAULT_MAX_RETRIES,
    ):
        """
        Initialize the Kawaa client.

        Args:
            api_key: Your Kawaa API key.
            base_url: API base URL (default: https://api.kawaa.com).
            timeout: Request timeout in seconds (default: 30).
            max_retries: Maximum number of retries for failed requests (default: 3).
        """
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.max_retries = max_retries
        self._client: Optional[httpx.Client] = None

    @property
    def client(self) -> httpx.Client:
        """Get or create the HTTP client."""
        if self._client is None or self._client.is_closed:
            self._client = httpx.Client(
                base_url=self.base_url,
                timeout=self.timeout,
                headers={
                    "X-Api-Key": self.api_key,
                    "Content-Type": "application/json",
                    "User-Agent": "kawaa-python/0.1.0",
                },
            )
        return self._client

    def close(self) -> None:
        """Close the HTTP client."""
        if self._client is not None:
            self._client.close()
            self._client = None

    def __enter__(self) -> "Kawaa":
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    def _request(
        self,
        method: str,
        path: str,
        json: Optional[Dict[str, Any]] = None,
        params: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """
        Make an HTTP request with retry logic.

        Args:
            method: HTTP method (GET, POST, etc.).
            path: API path (e.g., /v1/verify).
            json: JSON body for POST requests.
            params: Query parameters.

        Returns:
            Response JSON as dict.

        Raises:
            KawaaError: On API errors.
            TimeoutError: On request timeout.
        """
        last_error: Optional[Exception] = None

        for attempt in range(self.max_retries):
            try:
                response = self.client.request(
                    method=method,
                    url=path,
                    json=json,
                    params=params,
                )

                # Parse response
                try:
                    data = response.json()
                except Exception:
                    data = {"error": response.text}

                # Check for errors
                if response.status_code >= 400:
                    # Don't retry client errors (except rate limits)
                    if response.status_code == 429:
                        retry_after = data.get("retry_after", 2 ** attempt)
                        if attempt < self.max_retries - 1:
                            time.sleep(retry_after)
                            continue
                    elif response.status_code < 500:
                        raise_for_status(response.status_code, data)

                    # Retry server errors
                    if attempt < self.max_retries - 1:
                        time.sleep(2 ** attempt)
                        continue

                    raise_for_status(response.status_code, data)

                return data

            except httpx.TimeoutException as e:
                last_error = e
                if attempt < self.max_retries - 1:
                    time.sleep(2 ** attempt)
                    continue

            except (RateLimitError, KawaaError):
                raise

            except Exception as e:
                last_error = e
                if attempt < self.max_retries - 1:
                    time.sleep(2 ** attempt)
                    continue

        if isinstance(last_error, httpx.TimeoutException):
            raise TimeoutError(f"Request timed out after {self.timeout}s")

        raise KawaaError(f"Request failed after {self.max_retries} attempts: {last_error}")

    # -------------------------------------------------------------------------
    # Single Email Verification
    # -------------------------------------------------------------------------

    def verify(
        self,
        email: str,
        *,
        check_mx: bool = True,
        check_smtp: bool = True,
        check_catch_all: bool = True,
        use_ai: bool = True,
    ) -> VerificationResult:
        """
        Verify a single email address.

        Args:
            email: Email address to verify.
            check_mx: Check MX records (default: True).
            check_smtp: Perform SMTP verification (default: True).
            check_catch_all: Detect catch-all domains (default: True).
            use_ai: Use AI for typo detection (default: True).

        Returns:
            VerificationResult with status, score, and flags.

        Example:
            >>> result = client.verify("john@gmail.com")
            >>> if result.is_valid:
            ...     print(f"Valid email with score {result.score}")
        """
        data = self._request(
            "POST",
            "/v1/verify",
            json={
                "email": email,
                "options": {
                    "check_mx": check_mx,
                    "check_smtp": check_smtp,
                    "check_catch_all": check_catch_all,
                    "use_ai": use_ai,
                },
            },
        )
        return VerificationResult.from_dict(data)

    # -------------------------------------------------------------------------
    # Batch Verification
    # -------------------------------------------------------------------------

    def verify_batch(
        self,
        emails: List[str],
        *,
        webhook_url: Optional[str] = None,
        webhook_secret: Optional[str] = None,
        check_mx: bool = True,
        check_smtp: bool = True,
        check_catch_all: bool = True,
        use_ai: bool = True,
    ) -> BatchJobResult:
        """
        Start a batch email verification job.

        Args:
            emails: List of email addresses to verify.
            webhook_url: URL to receive completion notification.
            webhook_secret: Secret for HMAC signature verification.
            check_mx: Check MX records (default: True).
            check_smtp: Perform SMTP verification (default: True).
            check_catch_all: Detect catch-all domains (default: True).
            use_ai: Use AI for typo detection (default: True).

        Returns:
            BatchJobResult with job_id and status.

        Example:
            >>> job = client.verify_batch(["a@example.com", "b@example.com"])
            >>> print(f"Job {job.job_id} started, {job.duplicates_removed} duplicates removed")
        """
        payload: Dict[str, Any] = {
            "emails": emails,
            "options": {
                "check_mx": check_mx,
                "check_smtp": check_smtp,
                "check_catch_all": check_catch_all,
                "use_ai": use_ai,
            },
        }

        if webhook_url:
            payload["webhook_url"] = webhook_url
        if webhook_secret:
            payload["webhook_secret"] = webhook_secret

        data = self._request("POST", "/v1/verify/batch", json=payload)
        return BatchJobResult.from_dict(data)

    def verify_batch_and_wait(
        self,
        emails: List[str],
        *,
        poll_interval: float = DEFAULT_POLL_INTERVAL,
        timeout: float = DEFAULT_POLL_TIMEOUT,
        **kwargs: Any,
    ) -> JobStatusResult:
        """
        Start a batch verification and wait for completion.

        Args:
            emails: List of email addresses to verify.
            poll_interval: Seconds between status checks (default: 2).
            timeout: Maximum seconds to wait (default: 300).
            **kwargs: Additional arguments passed to verify_batch.

        Returns:
            JobStatusResult with all verification results.

        Example:
            >>> result = client.verify_batch_and_wait(emails, timeout=600)
            >>> print(f"{result.valid_count} valid, {result.invalid_count} invalid")
        """
        job = self.verify_batch(emails, **kwargs)
        return self.wait_for_job(
            job.job_id,
            poll_interval=poll_interval,
            timeout=timeout,
        )

    # -------------------------------------------------------------------------
    # Job Management
    # -------------------------------------------------------------------------

    def get_job_status(
        self,
        job_id: str,
        *,
        include_results: bool = False,
    ) -> JobStatusResult:
        """
        Get the status of a batch verification job.

        Args:
            job_id: The job ID returned from verify_batch.
            include_results: Include individual results (default: False).

        Returns:
            JobStatusResult with progress and counts.

        Example:
            >>> status = client.get_job_status("job-123")
            >>> print(f"Progress: {status.progress_percent}%")
        """
        params = {"include_results": "true"} if include_results else None
        data = self._request("GET", f"/v1/jobs/{job_id}", params=params)
        return JobStatusResult.from_dict(data)

    def wait_for_job(
        self,
        job_id: str,
        *,
        poll_interval: float = DEFAULT_POLL_INTERVAL,
        timeout: float = DEFAULT_POLL_TIMEOUT,
    ) -> JobStatusResult:
        """
        Wait for a batch job to complete.

        Args:
            job_id: The job ID to wait for.
            poll_interval: Seconds between status checks (default: 2).
            timeout: Maximum seconds to wait (default: 300).

        Returns:
            JobStatusResult with all results.

        Raises:
            TimeoutError: If job doesn't complete within timeout.

        Example:
            >>> result = client.wait_for_job("job-123", timeout=600)
            >>> for r in result.results:
            ...     print(f"{r.email}: {r.status}")
        """
        start_time = time.time()

        while True:
            status = self.get_job_status(job_id, include_results=True)

            if status.is_completed:
                return status

            if status.is_failed:
                raise KawaaError(f"Job {job_id} failed")

            elapsed = time.time() - start_time
            if elapsed >= timeout:
                raise TimeoutError(
                    f"Job {job_id} did not complete within {timeout}s "
                    f"(progress: {status.progress_percent}%)"
                )

            time.sleep(poll_interval)

    def download_results(
        self,
        job_id: str,
        *,
        format: str = "json",
    ) -> Union[DownloadResult, str]:
        """
        Download results from a completed batch job.

        Args:
            job_id: The job ID to download results for.
            format: Output format ("json" or "csv").

        Returns:
            DownloadResult for JSON, raw CSV string for CSV format.

        Raises:
            JobNotCompleteError: If job is not yet completed.

        Example:
            >>> results = client.download_results("job-123")
            >>> for r in results.results:
            ...     print(f"{r.email}: {r.status}")
        """
        params = {"format": format}

        response = self.client.get(f"/v1/jobs/{job_id}/download", params=params)

        if response.status_code == 400:
            data = response.json()
            if "still" in data.get("error", "").lower():
                raise JobNotCompleteError(job_id, "processing", response_body=data)
            raise_for_status(response.status_code, data)

        if response.status_code >= 400:
            try:
                data = response.json()
            except Exception:
                data = {"error": response.text}
            raise_for_status(response.status_code, data)

        if format == "csv":
            return response.text

        return DownloadResult.from_dict(response.json())

    # -------------------------------------------------------------------------
    # Account
    # -------------------------------------------------------------------------

    def get_account(self) -> AccountInfo:
        """
        Get account information.

        Returns:
            AccountInfo with credits and plan details.

        Example:
            >>> account = client.get_account()
            >>> print(f"Credits remaining: {account.credits_remaining}")
        """
        data = self._request("GET", "/v1/account")
        return AccountInfo.from_dict(data)

    # -------------------------------------------------------------------------
    # Email Finder
    # -------------------------------------------------------------------------

    def find(
        self,
        first_name: str,
        last_name: str,
        domain: str,
    ) -> FindResult:
        """
        Find a business email address by name and domain.

        This uses pattern matching and verification to find the most likely
        email address for a person at a given company domain.

        Args:
            first_name: Person's first name.
            last_name: Person's last name.
            domain: Company domain (e.g., "company.com").

        Returns:
            FindResult with the found email and confidence score.

        Example:
            >>> result = client.find("John", "Smith", "company.com")
            >>> if result.found:
            ...     print(f"Found: {result.email} ({result.confidence}% confidence)")
            >>> else:
            ...     print("Email not found")

        Note:
            This uses 5 credits per request (covers up to 8 pattern verifications).
        """
        data = self._request(
            "POST",
            "/v1/find",
            json={
                "first_name": first_name,
                "last_name": last_name,
                "domain": domain,
            },
        )
        return FindResult.from_dict(data)


class AsyncKawaa:
    """
    Async Kawaa Email Verification API client.

    Example:
        >>> import asyncio
        >>> from kawaa import AsyncKawaa
        >>>
        >>> async def main():
        ...     async with AsyncKawaa("your-api-key") as client:
        ...         result = await client.verify("test@example.com")
        ...         print(result.status)
        >>>
        >>> asyncio.run(main())
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
        max_retries: int = DEFAULT_MAX_RETRIES,
    ):
        """
        Initialize the async Kawaa client.

        Args:
            api_key: Your Kawaa API key.
            base_url: API base URL (default: https://api.kawaa.com).
            timeout: Request timeout in seconds (default: 30).
            max_retries: Maximum number of retries for failed requests (default: 3).
        """
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.max_retries = max_retries
        self._client: Optional[httpx.AsyncClient] = None

    @property
    def client(self) -> httpx.AsyncClient:
        """Get or create the async HTTP client."""
        if self._client is None or self._client.is_closed:
            self._client = httpx.AsyncClient(
                base_url=self.base_url,
                timeout=self.timeout,
                headers={
                    "X-Api-Key": self.api_key,
                    "Content-Type": "application/json",
                    "User-Agent": "kawaa-python/0.1.0",
                },
            )
        return self._client

    async def close(self) -> None:
        """Close the async HTTP client."""
        if self._client is not None:
            await self._client.aclose()
            self._client = None

    async def __aenter__(self) -> "AsyncKawaa":
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.close()

    async def _request(
        self,
        method: str,
        path: str,
        json: Optional[Dict[str, Any]] = None,
        params: Optional[Dict[str, Any]] = None,
    ) -> Dict[str, Any]:
        """Make an async HTTP request with retry logic."""
        import asyncio

        last_error: Optional[Exception] = None

        for attempt in range(self.max_retries):
            try:
                response = await self.client.request(
                    method=method,
                    url=path,
                    json=json,
                    params=params,
                )

                try:
                    data = response.json()
                except Exception:
                    data = {"error": response.text}

                if response.status_code >= 400:
                    if response.status_code == 429:
                        retry_after = data.get("retry_after", 2 ** attempt)
                        if attempt < self.max_retries - 1:
                            await asyncio.sleep(retry_after)
                            continue
                    elif response.status_code < 500:
                        raise_for_status(response.status_code, data)

                    if attempt < self.max_retries - 1:
                        await asyncio.sleep(2 ** attempt)
                        continue

                    raise_for_status(response.status_code, data)

                return data

            except httpx.TimeoutException as e:
                last_error = e
                if attempt < self.max_retries - 1:
                    await asyncio.sleep(2 ** attempt)
                    continue

            except (RateLimitError, KawaaError):
                raise

            except Exception as e:
                last_error = e
                if attempt < self.max_retries - 1:
                    await asyncio.sleep(2 ** attempt)
                    continue

        if isinstance(last_error, httpx.TimeoutException):
            raise TimeoutError(f"Request timed out after {self.timeout}s")

        raise KawaaError(f"Request failed after {self.max_retries} attempts: {last_error}")

    async def verify(
        self,
        email: str,
        *,
        check_mx: bool = True,
        check_smtp: bool = True,
        check_catch_all: bool = True,
        use_ai: bool = True,
    ) -> VerificationResult:
        """Verify a single email address (async)."""
        data = await self._request(
            "POST",
            "/v1/verify",
            json={
                "email": email,
                "options": {
                    "check_mx": check_mx,
                    "check_smtp": check_smtp,
                    "check_catch_all": check_catch_all,
                    "use_ai": use_ai,
                },
            },
        )
        return VerificationResult.from_dict(data)

    async def verify_batch(
        self,
        emails: List[str],
        *,
        webhook_url: Optional[str] = None,
        webhook_secret: Optional[str] = None,
        **kwargs: Any,
    ) -> BatchJobResult:
        """Start a batch email verification job (async)."""
        payload: Dict[str, Any] = {
            "emails": emails,
            "options": {
                "check_mx": kwargs.get("check_mx", True),
                "check_smtp": kwargs.get("check_smtp", True),
                "check_catch_all": kwargs.get("check_catch_all", True),
                "use_ai": kwargs.get("use_ai", True),
            },
        }

        if webhook_url:
            payload["webhook_url"] = webhook_url
        if webhook_secret:
            payload["webhook_secret"] = webhook_secret

        data = await self._request("POST", "/v1/verify/batch", json=payload)
        return BatchJobResult.from_dict(data)

    async def get_job_status(
        self,
        job_id: str,
        *,
        include_results: bool = False,
    ) -> JobStatusResult:
        """Get the status of a batch verification job (async)."""
        params = {"include_results": "true"} if include_results else None
        data = await self._request("GET", f"/v1/jobs/{job_id}", params=params)
        return JobStatusResult.from_dict(data)

    async def wait_for_job(
        self,
        job_id: str,
        *,
        poll_interval: float = DEFAULT_POLL_INTERVAL,
        timeout: float = DEFAULT_POLL_TIMEOUT,
    ) -> JobStatusResult:
        """Wait for a batch job to complete (async)."""
        import asyncio

        start_time = time.time()

        while True:
            status = await self.get_job_status(job_id, include_results=True)

            if status.is_completed:
                return status

            if status.is_failed:
                raise KawaaError(f"Job {job_id} failed")

            elapsed = time.time() - start_time
            if elapsed >= timeout:
                raise TimeoutError(
                    f"Job {job_id} did not complete within {timeout}s "
                    f"(progress: {status.progress_percent}%)"
                )

            await asyncio.sleep(poll_interval)

    async def verify_batch_and_wait(
        self,
        emails: List[str],
        *,
        poll_interval: float = DEFAULT_POLL_INTERVAL,
        timeout: float = DEFAULT_POLL_TIMEOUT,
        **kwargs: Any,
    ) -> JobStatusResult:
        """Start a batch verification and wait for completion (async)."""
        job = await self.verify_batch(emails, **kwargs)
        return await self.wait_for_job(
            job.job_id,
            poll_interval=poll_interval,
            timeout=timeout,
        )

    async def download_results(
        self,
        job_id: str,
        *,
        format: str = "json",
    ) -> Union[DownloadResult, str]:
        """Download results from a completed batch job (async)."""
        params = {"format": format}

        response = await self.client.get(f"/v1/jobs/{job_id}/download", params=params)

        if response.status_code == 400:
            data = response.json()
            if "still" in data.get("error", "").lower():
                raise JobNotCompleteError(job_id, "processing", response_body=data)
            raise_for_status(response.status_code, data)

        if response.status_code >= 400:
            try:
                data = response.json()
            except Exception:
                data = {"error": response.text}
            raise_for_status(response.status_code, data)

        if format == "csv":
            return response.text

        return DownloadResult.from_dict(response.json())

    async def get_account(self) -> AccountInfo:
        """Get account information (async)."""
        data = await self._request("GET", "/v1/account")
        return AccountInfo.from_dict(data)

    async def find(
        self,
        first_name: str,
        last_name: str,
        domain: str,
    ) -> FindResult:
        """
        Find a business email address by name and domain (async).

        Args:
            first_name: Person's first name.
            last_name: Person's last name.
            domain: Company domain (e.g., "company.com").

        Returns:
            FindResult with the found email and confidence score.
        """
        data = await self._request(
            "POST",
            "/v1/find",
            json={
                "first_name": first_name,
                "last_name": last_name,
                "domain": domain,
            },
        )
        return FindResult.from_dict(data)
