"""
VeriRoute Intel SDK Client

Main client class for interacting with the VeriRoute Intel API.
"""

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

import httpx

from .errors import (
    AuthenticationError,
    InsufficientBalanceError,
    InternationalNotSupportedError,
    InvalidPhoneError,
    NetworkError,
    RateLimitError,
    TimeoutError,
    VeriRouteError,
)
from .types import (
    AnalyticsPeriod,
    AnalyticsResult,
    BulkCnamResult,
    BulkError,
    BulkLrnResult,
    BulkSpamResult,
    CnamData,
    CnamResult,
    EnhancedLrnData,
    LineType,
    LrnResult,
    MessagingData,
    MessagingResult,
    ReportType,
    SpamResult,
    SpamReportResult,
    SpamType,
    TrustData,
    TrustResult,
    TrustResultV2,
    UsageResult,
)

SDK_VERSION = "1.0.0"
DEFAULT_BASE_URL = "https://api-service.verirouteintel.io"
DEFAULT_TIMEOUT = 30.0
DEFAULT_RETRIES = 3


class VeriRoute:
    """
    VeriRoute Intel API Client.

    Example:
        >>> from verirouteintel import VeriRoute
        >>> vri = VeriRoute('your_api_key')
        >>>
        >>> # CNAM lookup
        >>> caller = vri.cnam('+15551234567')
        >>> print(caller.cnam)  # "JOHN DOE"
        >>>
        >>> # LRN with enhanced data
        >>> info = vri.lrn('+15551234567', include_enhanced=True)
        >>> print(info.carrier)  # "Verizon Wireless"
        >>> print(info.enhanced.city)  # "New York"
    """

    def __init__(
        self,
        api_key: str,
        *,
        base_url: str = DEFAULT_BASE_URL,
        timeout: float = DEFAULT_TIMEOUT,
        retries: int = DEFAULT_RETRIES,
    ):
        """
        Initialize VeriRoute client.

        Args:
            api_key: Your API key from verirouteintel.com/dashboard
            base_url: Base URL for API requests
            timeout: Request timeout in seconds
            retries: Number of retry attempts for failed requests
        """
        if not api_key:
            raise AuthenticationError("API key is required")

        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.retries = retries

        self._client = httpx.Client(
            base_url=self.base_url,
            timeout=timeout,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "Accept": "application/json",
                "User-Agent": f"verirouteintel-python/{SDK_VERSION}",
            },
        )

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

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

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

    # =========================================================================
    # HTTP Layer
    # =========================================================================

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

        for attempt in range(self.retries + 1):
            try:
                response = self._client.request(
                    method,
                    path,
                    json=json,
                    params=params,
                )

                # Handle rate limiting with retry
                if response.status_code == 429 and attempt < self.retries:
                    retry_after = int(response.headers.get("Retry-After", "1"))
                    time.sleep(retry_after)
                    continue

                # Parse response
                data = response.json()

                # Handle errors
                if not response.is_success:
                    raise self._handle_error_response(response.status_code, data)

                return data

            except httpx.TimeoutException:
                last_error = TimeoutError()
                if attempt < self.retries:
                    time.sleep(2**attempt)
                    continue

            except httpx.RequestError as e:
                last_error = NetworkError(str(e))
                if attempt < self.retries:
                    time.sleep(2**attempt)
                    continue

            except VeriRouteError:
                raise

        raise last_error or NetworkError()

    def _handle_error_response(
        self, status_code: int, data: Dict[str, Any]
    ) -> VeriRouteError:
        """Convert API error response to exception."""
        error = data.get("error", {})

        # Handle both dict and string error formats
        if isinstance(error, str):
            code = data.get("code", "UNKNOWN_ERROR")
            message = error
            details = {}
        else:
            code = error.get("code", "UNKNOWN_ERROR")
            message = error.get("message", f"Request failed with status {status_code}")
            details = error.get("details", {})

        if code in ("AUTH_REQUIRED", "AUTH_FAILED"):
            return AuthenticationError(message)

        if code == "RATE_LIMIT_EXCEEDED":
            return RateLimitError(message)

        if code == "INSUFFICIENT_BALANCE":
            return InsufficientBalanceError(message)

        if code in ("INVALID_PHONE_NUMBER", "MISSING_PHONE_NUMBER"):
            return InvalidPhoneError(message, details.get("phone_number"))

        if code == "INTERNATIONAL_NOT_SUPPORTED":
            return InternationalNotSupportedError(
                message,
                details.get("phone_number"),
                details.get("detected_country_code"),
            )

        return VeriRouteError(message, code, status_code, details)

    # =========================================================================
    # CNAM Methods
    # =========================================================================

    def cnam(
        self,
        phone_number: str,
        *,
        include_spam: bool = False,
    ) -> CnamResult:
        """
        Look up caller name (CNAM) for a phone number.

        Args:
            phone_number: Phone number in E.164 or national format
            include_spam: Include spam detection in response

        Returns:
            CnamResult with caller name

        Example:
            >>> result = vri.cnam('+15551234567')
            >>> print(result.cnam)
            JOHN DOE

            >>> result = vri.cnam('+15551234567', include_spam=True)
            >>> print(result.spam_type)
            NONE
        """
        response = self._request(
            "POST",
            "/api/v1/cnam",
            json={
                "phone_number": phone_number,
                "include_spam": include_spam,
            },
        )

        data = response.get("data", response)
        return CnamResult(
            number=data.get("number", phone_number),
            cnam=data.get("cnam"),
            spam_type=data.get("spam_type"),
        )

    def cnam_bulk(
        self,
        phone_numbers: List[str],
        *,
        include_spam: bool = False,
    ) -> BulkCnamResult:
        """
        Bulk CNAM lookup for multiple phone numbers (up to 1000).

        Args:
            phone_numbers: List of phone numbers
            include_spam: Include spam detection

        Returns:
            BulkCnamResult with results and errors
        """
        if len(phone_numbers) > 1000:
            raise InvalidPhoneError("Maximum 1000 phone numbers per bulk request")

        response = self._request(
            "POST",
            "/api/v1/cnam/bulk",
            json={
                "phone_numbers": phone_numbers,
                "include_spam": include_spam,
            },
        )

        results = [
            CnamResult(
                number=r.get("number", r.get("phone_number", "")),
                cnam=r.get("cnam"),
                spam_type=r.get("spam_type"),
            )
            for r in response.get("results", [])
        ]

        errors = [
            BulkError(
                phone_number=e.get("phone_number", ""),
                error=e.get("error", "Unknown error"),
            )
            for e in response.get("errors", [])
        ]

        return BulkCnamResult(
            results=results,
            errors=errors,
            total=len(phone_numbers),
            successful=len(results),
            failed=len(errors),
        )

    # =========================================================================
    # LRN Methods
    # =========================================================================

    def lrn(
        self,
        phone_number: str,
        *,
        lrn_only: bool = False,
        include_enhanced: bool = False,
        messaging_lookup: bool = False,
        include_cnam: bool = False,
        include_trust: bool = False,
    ) -> LrnResult:
        """
        Look up carrier, line type, and routing information.

        Args:
            phone_number: Phone number to look up
            lrn_only: Return only LRN without additional data
            include_enhanced: Include city, state, ZIP, timezone, etc.
            messaging_lookup: Include messaging provider information
            include_cnam: Include CNAM (caller name) data
            include_trust: Include trust/reputation data with reputation_score

        Returns:
            LrnResult with carrier and line type

        Example:
            >>> info = vri.lrn('+15551234567')
            >>> print(info.carrier)
            Verizon Wireless
            >>> print(info.line_type)
            mobile

            >>> info = vri.lrn('+15551234567', include_enhanced=True)
            >>> print(info.enhanced.city)
            New York

            >>> info = vri.lrn('+15551234567', include_cnam=True, include_trust=True)
            >>> print(info.cnam.caller_name)
            ACME CORP
            >>> print(info.trust.reputation_score)
            85
        """
        response = self._request(
            "POST",
            "/api/v1/lrn",
            json={
                "phone_number": phone_number,
                "lrn_only": lrn_only,
                "include_enhanced_lrn": include_enhanced,
                "messaging_lookup": messaging_lookup,
                "include_cnam": include_cnam,
                "include_trust": include_trust,
            },
        )

        return self._transform_lrn_response(response)

    def lrn_bulk(
        self,
        phone_numbers: List[str],
        *,
        include_enhanced: bool = False,
        messaging_lookup: bool = False,
        include_cnam: bool = False,
        include_trust: bool = False,
    ) -> BulkLrnResult:
        """
        Bulk LRN lookup for multiple phone numbers (up to 1000).

        Args:
            phone_numbers: List of phone numbers to look up
            include_enhanced: Include city, state, ZIP, timezone, etc.
            messaging_lookup: Include messaging provider information
            include_cnam: Include CNAM (caller name) data for each number
            include_trust: Include trust/reputation data for each number

        Returns:
            BulkLrnResult with results and errors
        """
        if len(phone_numbers) > 1000:
            raise InvalidPhoneError("Maximum 1000 phone numbers per bulk request")

        response = self._request(
            "POST",
            "/api/v1/lrn/bulk",
            json={
                "phone_numbers": phone_numbers,
                "include_enhanced_lrn": include_enhanced,
                "messaging_lookup": messaging_lookup,
                "include_cnam": include_cnam,
                "include_trust": include_trust,
            },
        )

        results = [
            self._transform_lrn_response(r) for r in response.get("results", [])
        ]

        errors = [
            BulkError(
                phone_number=e.get("phone_number", ""),
                error=e.get("error", "Unknown error"),
            )
            for e in response.get("errors", [])
        ]

        return BulkLrnResult(
            results=results,
            errors=errors,
            total=len(phone_numbers),
            successful=len(results),
            failed=len(errors),
        )

    def _transform_lrn_response(self, data: Dict[str, Any]) -> LrnResult:
        """Transform raw LRN response to typed result."""
        enhanced_data = data.get("enhanced_lrn") or data.get("enhanced")
        messaging_data = data.get("messaging")
        cnam_data = data.get("cnam")
        trust_data = data.get("trust")

        enhanced: Optional[EnhancedLrnData] = None
        if enhanced_data:
            enhanced = EnhancedLrnData(
                carrier=enhanced_data.get("carrier", ""),
                carrier_type=enhanced_data.get("carrier_type", ""),
                city=enhanced_data.get("city", ""),
                state=enhanced_data.get("state", ""),
                zip_code=enhanced_data.get("zip_code", ""),
                county=enhanced_data.get("county", ""),
                timezone=enhanced_data.get("timezone", ""),
                rate_center=enhanced_data.get("rate_center", ""),
                lata=enhanced_data.get("lata", ""),
                ocn=enhanced_data.get("ocn", ""),
            )

        messaging: Optional[MessagingData] = None
        if messaging_data:
            messaging = MessagingData(
                provider=messaging_data.get("messaging_provider", messaging_data.get("provider", "")),
                enabled=messaging_data.get("messaging_enabled", messaging_data.get("enabled", False)),
                country=messaging_data.get("messaging_country", messaging_data.get("country", "")),
                country_code=messaging_data.get("messaging_country_code", messaging_data.get("country_code", "")),
                reference_id=messaging_data.get("reference_id"),
            )

        cnam: Optional[CnamData] = None
        if cnam_data:
            cnam = CnamData(
                caller_name=cnam_data.get("caller_name"),
            )

        trust: Optional[TrustData] = None
        if trust_data:
            trust = TrustData(
                is_spam=trust_data.get("is_spam", False),
                is_robocall=trust_data.get("is_robocall", False),
                is_scam=trust_data.get("is_scam", False),
                spam_type=trust_data.get("spam_type", "NONE"),
                reputation_score=trust_data.get("reputation_score", 85),
                trust_level=trust_data.get("trust_level", "high"),
                last_updated=trust_data.get("last_updated"),
            )

        # Extract carrier and line_type from enhanced data if not at top level
        carrier = data.get("carrier") or (enhanced_data.get("carrier", "") if enhanced_data else "")
        line_type = data.get("line_type") or (enhanced_data.get("carrier_type", "unknown") if enhanced_data else "unknown")

        return LrnResult(
            phone_number=data.get("phone_number", ""),
            lrn=data.get("lrn"),
            lrn_activated_at=data.get("lrn_activated_at"),
            carrier=carrier,
            line_type=line_type,
            enhanced=enhanced,
            messaging=messaging,
            cnam=cnam,
            trust=trust,
        )

    # =========================================================================
    # Trust / Spam Methods
    # =========================================================================

    def trust(self, phone_number: str) -> TrustResult:
        """
        Check phone number reputation and spam status.

        Args:
            phone_number: Phone number to check

        Returns:
            TrustResult with spam/scam/robocall flags

        Example:
            >>> trust = vri.trust('+15551234567')
            >>> print(trust.is_spam)
            False
            >>> print(trust.complaint_count)
            0
        """
        response = self._request(
            "POST",
            "/api/v1/trust",
            json={"phone_number": phone_number},
        )

        data = response.get("data", response)
        return TrustResult(
            number=data.get("number", phone_number),
            is_spam=data.get("is_spam", False),
            is_robocall=data.get("is_robocall", False),
            is_scam=data.get("is_scam", False),
            spam_type=data.get("spam_type", "NONE"),
            complaint_count=data.get("complaint_count", 0),
            subjects=data.get("subjects", []),
            first_reported=data.get("first_reported"),
            last_reported=data.get("last_reported"),
            details=data.get("details", ""),
        )

    def trust_v2(self, phone_number: str) -> TrustResultV2:
        """
        Check phone number reputation with quantitative scoring (v2).

        Returns enhanced trust data including:
        - reputation_score: 0-100 (higher = more trustworthy)
        - trust_level: high (70+), medium (40-69), low (<40)
        - last_updated: ISO 8601 timestamp

        Args:
            phone_number: Phone number to check

        Returns:
            TrustResultV2 with reputation scoring

        Example:
            >>> trust = vri.trust_v2('+15551234567')
            >>> print(trust.reputation_score)
            85
            >>> print(trust.trust_level)
            high
        """
        response = self._request(
            "POST",
            "/api/v2/trust",
            json={"phone_number": phone_number},
        )

        data = response.get("data", response)
        return TrustResultV2(
            number=data.get("number", phone_number),
            is_spam=data.get("is_spam", False),
            is_robocall=data.get("is_robocall", False),
            is_scam=data.get("is_scam", False),
            spam_type=data.get("spam_type", "NONE"),
            complaint_count=data.get("complaint_count", 0),
            reputation_score=data.get("reputation_score", 85),
            trust_level=data.get("trust_level", "high"),
            last_updated=data.get("last_updated", ""),
            subjects=data.get("subjects", []),
            first_reported=data.get("first_reported"),
            last_reported=data.get("last_reported"),
            details=data.get("details", ""),
        )

    def spam(self, phone_number: str) -> SpamResult:
        """Quick spam check (lighter weight than trust)."""
        response = self._request(
            "POST",
            "/api/v1/spam",
            json={"phone_number": phone_number, "check_spam": True},
        )

        return SpamResult(
            phone_number=response.get("phone_number", phone_number),
            is_spam=response.get("is_spam", False),
            is_robocall=response.get("is_robocall", False),
            is_scam=response.get("is_scam", False),
            spam_type=response.get("spam_type", "NONE"),
            cached=response.get("cached", False),
            source=response.get("source", "unknown"),
        )

    def spam_batch(self, phone_numbers: List[str]) -> BulkSpamResult:
        """Batch spam check for multiple numbers."""
        response = self._request(
            "POST",
            "/api/v1/spam/batch",
            json={"phone_numbers": phone_numbers},
        )

        results = [
            SpamResult(
                phone_number=r.get("phone_number", ""),
                is_spam=r.get("is_spam", False),
                is_robocall=r.get("is_robocall", False),
                is_scam=r.get("is_scam", False),
                spam_type=r.get("spam_type", "NONE"),
                cached=r.get("cached", False),
                source=r.get("source", "unknown"),
            )
            for r in response.get("results", [])
        ]

        errors = [
            BulkError(
                phone_number=e.get("phone_number", ""),
                error=e.get("error", "Unknown error"),
            )
            for e in response.get("errors", [])
        ]

        return BulkSpamResult(
            results=results,
            errors=errors,
            total=len(phone_numbers),
            successful=len(results),
            failed=len(errors),
        )

    def spam_report(
        self,
        phone_number: str,
        *,
        report_type: ReportType,
        details: Optional[str] = None,
        message_content: Optional[str] = None,
        carrier_ocn: Optional[str] = None,
    ) -> SpamReportResult:
        """
        Report a phone number as spam/scam/robocall.

        Args:
            phone_number: Phone number to report
            report_type: Type of spam (spam, robocall, scam, etc.)
            details: Additional details about the spam
            message_content: Content of spam message (for SMS)
            carrier_ocn: Carrier OCN if known

        Example:
            >>> vri.spam_report('+15551234567',
            ...     report_type='robocall',
            ...     details='Automated warranty scam call'
            ... )
        """
        response = self._request(
            "POST",
            "/api/v1/spam/report",
            json={
                "phone_number": phone_number,
                "report_type": report_type,
                "details": details,
                "message_content": message_content,
                "carrier_ocn": carrier_ocn,
            },
        )

        return SpamReportResult(
            success=response.get("success", True),
            report_id=response.get("report_id", 0),
            carrier_id=response.get("carrier_id"),
            carrier_name=response.get("carrier_name"),
            message=response.get("message", "Report submitted"),
        )

    # =========================================================================
    # Messaging Methods
    # =========================================================================

    def messaging(self, phone_number: str) -> MessagingResult:
        """
        Look up messaging provider for a phone number.

        Example:
            >>> msg = vri.messaging('+15551234567')
            >>> print(msg.messaging_provider)
            Verizon Wireless
        """
        response = self._request(
            "POST",
            "/api/v1/messaging",
            json={"phone_number": phone_number},
        )

        return MessagingResult(
            phone_number=response.get("phone_number", phone_number),
            messaging_provider=response.get("messaging_provider", ""),
            messaging_enabled=response.get("messaging_enabled", False),
            messaging_country=response.get("messaging_country", ""),
            messaging_country_code=response.get("messaging_country_code", ""),
            reference_id=response.get("reference_id", ""),
        )

    # =========================================================================
    # Analytics & Usage
    # =========================================================================

    def analytics(
        self,
        *,
        preset: Optional[str] = None,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
    ) -> AnalyticsResult:
        """
        Get usage analytics.

        Args:
            preset: Time range preset ('7d', '30d', '90d', '365d')
            start_date: Custom start date (YYYY-MM-DD)
            end_date: Custom end date (YYYY-MM-DD)

        Example:
            >>> analytics = vri.analytics(preset='30d')
            >>> print(analytics.total_lookups)
            1250
        """
        params: Dict[str, str] = {}
        if preset:
            params["preset"] = preset
        if start_date:
            params["start_date"] = start_date
        if end_date:
            params["end_date"] = end_date

        response = self._request("GET", "/api/v1/analytics", params=params)
        data = response.get("data", response)

        period_data = data.get("period", {})
        return AnalyticsResult(
            period=AnalyticsPeriod(
                start=period_data.get("start", ""),
                end=period_data.get("end", ""),
            ),
            total_lookups=data.get("total_lookups", 0),
            carrier_type_breakdown=data.get("carrier_type_breakdown", {}),
            spam_breakdown=data.get("spam_breakdown", {}),
            provider_category_breakdown=data.get("provider_category_breakdown", {}),
            geographic_breakdown=data.get("geographic_breakdown", {}),
            trends=data.get("trends", []),
        )

    def usage(
        self,
        *,
        period: Optional[str] = None,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
        group_by: Optional[str] = None,
    ) -> UsageResult:
        """
        Get usage report for current API key.

        Args:
            period: Convenience date range: 'day' (24h), 'week' (7d), or 'month' (30d).
                    Defaults to 'month' if no dates provided.
            start_date: Custom start date (YYYY-MM-DD). Overrides period if provided.
            end_date: Custom end date (YYYY-MM-DD). Overrides period if provided.
            group_by: Time series grouping: 'day', 'week', or 'month'. Defaults to 'day'.

        Returns:
            UsageResult with usage statistics

        Example:
            >>> # Get last 7 days of usage
            >>> usage = vri.usage(period='week')
            >>> print(usage.total_lookups)
            150

            >>> # Get usage for custom date range
            >>> usage = vri.usage(start_date='2024-12-01', end_date='2024-12-31')
        """
        params: Dict[str, str] = {}
        if period:
            params["period"] = period
        if start_date:
            params["start_date"] = start_date
        if end_date:
            params["end_date"] = end_date
        if group_by:
            params["group_by"] = group_by

        response = self._request("GET", "/api/v1/reports/usage", params=params)
        data = response.get("data", response)

        period_data = data.get("period", {})
        summary = data.get("summary", {})

        return UsageResult(
            total_lookups=summary.get("total_lookups", data.get("total_lookups", 0)),
            total_spent=summary.get("total_spent", 0.0),
            by_product=data.get("by_product", {}),
            by_interface=data.get("by_interface", {}),
            spam_breakdown=data.get("spam_breakdown", {}),
            period=AnalyticsPeriod(
                start=period_data.get("start", ""),
                end=period_data.get("end", ""),
            ),
            time_series=data.get("time_series", []),
        )

    def validate_key(self) -> bool:
        """Validate API key. Returns True if valid."""
        try:
            self._request("POST", "/api/v1/auth/validate-key", json={"api_key": self.api_key})
            return True
        except VeriRouteError:
            return False


# Alias for shorter import
VRI = VeriRoute
