"""
SyncroRMM integration service for Quote Wizard.

This module handles all interactions with the SyncroRMM API for lead creation
and customer duplicate detection when quotes are submitted.

API Documentation: https://api-docs.syncromsp.com/
"""

import logging
import os
from dataclasses import dataclass
from datetime import datetime
from decimal import Decimal
from typing import Optional, TYPE_CHECKING

import httpx

if TYPE_CHECKING:
    from api.models.quote import Quote

logger = logging.getLogger(__name__)

SYNCRO_API_BASE_URL = os.environ.get(
    "SYNCRO_API_BASE_URL", "https://computerguru.syncromsp.com/api/v1"
)
SYNCRO_API_KEY = os.environ.get("SYNCRO_API_KEY", "")

# HTTP client configuration
SYNCRO_TIMEOUT_SECONDS = 30.0
SYNCRO_CONNECT_TIMEOUT_SECONDS = 10.0


@dataclass
class CustomerCheckResult:
    """Result of checking for an existing customer in Syncro."""
    exists: bool
    customer_id: Optional[str] = None
    customer_name: Optional[str] = None
    match_type: Optional[str] = None  # 'email' or 'business_name'


@dataclass
class LeadCreationResult:
    """Result of creating a lead in Syncro."""
    success: bool
    lead_id: Optional[str] = None
    error: Optional[str] = None


class SyncroService:
    """
    Service for interacting with the SyncroRMM API.

    Handles customer duplicate checking and lead creation for the Quote Wizard.
    All API calls are made asynchronously to avoid blocking quote submission.

    Example:
        ```python
        syncro = SyncroService()

        # Check for existing customer
        result = await syncro.check_existing_customer(
            email="contact@company.com",
            business_name="Company Inc"
        )
        if result.exists:
            print(f"Customer exists: {result.customer_name}")

        # Create lead from quote
        lead_result = await syncro.create_lead(quote)
        if lead_result.success:
            print(f"Lead created: {lead_result.lead_id}")
        ```
    """

    def __init__(
        self,
        api_base_url: str = SYNCRO_API_BASE_URL,
        api_key: str = SYNCRO_API_KEY,
        timeout: float = SYNCRO_TIMEOUT_SECONDS,
        connect_timeout: float = SYNCRO_CONNECT_TIMEOUT_SECONDS
    ):
        """
        Initialize the SyncroService.

        Args:
            api_base_url: Base URL for the Syncro API
            api_key: API key for authentication
            timeout: Total request timeout in seconds
            connect_timeout: Connection timeout in seconds
        """
        self.api_base_url = api_base_url.rstrip('/')
        self.api_key = api_key
        self.timeout = httpx.Timeout(timeout, connect=connect_timeout)

    def _get_client(self) -> httpx.AsyncClient:
        """
        Create an async HTTP client with configured settings.

        Returns:
            httpx.AsyncClient: Configured HTTP client
        """
        return httpx.AsyncClient(
            timeout=self.timeout,
            headers={
                "Content-Type": "application/json",
                "Accept": "application/json"
            }
        )

    def _build_url(self, endpoint: str, **params) -> str:
        """
        Build a full API URL with the api_key parameter.

        Args:
            endpoint: API endpoint path (e.g., '/customers')
            **params: Additional query parameters

        Returns:
            str: Full URL with query parameters
        """
        url = f"{self.api_base_url}{endpoint}"
        query_params = {"api_key": self.api_key, **params}

        # Build query string
        query_string = "&".join(
            f"{key}={httpx.URL('').copy_with(params={key: str(value)}).params[key]}"
            for key, value in query_params.items()
            if value is not None
        )

        return f"{url}?{query_string}"

    async def check_existing_customer(
        self,
        email: str,
        business_name: Optional[str] = None
    ) -> CustomerCheckResult:
        """
        Check if a customer already exists in Syncro.

        Performs a two-stage check:
        1. Search by email address (primary)
        2. Search by business name if no email match (secondary)

        Args:
            email: Contact email address to search for
            business_name: Optional business name for secondary search

        Returns:
            CustomerCheckResult: Object containing match status and details

        Note:
            This method handles errors gracefully and returns a non-match
            result if the API is unavailable, to avoid blocking quote submission.
        """
        async with self._get_client() as client:
            # First, check by email
            try:
                email_result = await self._search_customers_by_email(client, email)
                if email_result.exists:
                    return email_result
            except Exception as e:
                logger.warning(
                    f"Syncro email search failed for {email}: {e}",
                    exc_info=True
                )
                # Continue to business name search if email search fails

            # If no email match, try business name
            if business_name:
                try:
                    name_result = await self._search_customers_by_business_name(
                        client, business_name
                    )
                    if name_result.exists:
                        return name_result
                except Exception as e:
                    logger.warning(
                        f"Syncro business name search failed for {business_name}: {e}",
                        exc_info=True
                    )

        # No matches found
        return CustomerCheckResult(exists=False)

    async def _search_customers_by_email(
        self,
        client: httpx.AsyncClient,
        email: str
    ) -> CustomerCheckResult:
        """
        Search for customers by email address.

        Args:
            client: HTTP client instance
            email: Email address to search for

        Returns:
            CustomerCheckResult: Match result

        Raises:
            httpx.HTTPError: If the API request fails
        """
        url = self._build_url("/customers", email=email)

        response = await client.get(url)
        response.raise_for_status()

        data = response.json()
        customers = data.get("customers", [])

        if customers:
            customer = customers[0]
            return CustomerCheckResult(
                exists=True,
                customer_id=str(customer.get("id")),
                customer_name=customer.get("business_name") or customer.get("fullname"),
                match_type="email"
            )

        return CustomerCheckResult(exists=False)

    async def _search_customers_by_business_name(
        self,
        client: httpx.AsyncClient,
        business_name: str
    ) -> CustomerCheckResult:
        """
        Search for customers by business name.

        Args:
            client: HTTP client instance
            business_name: Business name to search for

        Returns:
            CustomerCheckResult: Match result

        Raises:
            httpx.HTTPError: If the API request fails
        """
        url = self._build_url("/customers", business_name=business_name)

        response = await client.get(url)
        response.raise_for_status()

        data = response.json()
        customers = data.get("customers", [])

        if customers:
            # Look for exact match or very close match
            normalized_search = business_name.lower().strip()
            for customer in customers:
                customer_name = customer.get("business_name", "").lower().strip()
                if customer_name == normalized_search:
                    return CustomerCheckResult(
                        exists=True,
                        customer_id=str(customer.get("id")),
                        customer_name=customer.get("business_name"),
                        match_type="business_name"
                    )

        return CustomerCheckResult(exists=False)

    async def create_lead(self, quote: "Quote") -> LeadCreationResult:
        """
        Create a lead in Syncro from a submitted quote.

        Builds a formatted lead with quote details in the notes field.

        Args:
            quote: Quote object with contact info and items

        Returns:
            LeadCreationResult: Object containing success status and lead ID

        Note:
            This method handles errors gracefully to avoid blocking quote
            submission. Errors are logged but not raised.
        """
        if not quote.contact_email:
            return LeadCreationResult(
                success=False,
                error="Quote has no contact email"
            )

        # Parse contact name into first/last
        first_name, last_name = self._parse_contact_name(quote.contact_name or "")

        # Build formatted notes with quote summary
        notes = self._build_quote_summary(quote)

        lead_data = {
            "business_name": quote.company_name or "",
            "first_name": first_name,
            "last_name": last_name,
            "email": quote.contact_email,
            "phone": quote.contact_phone or "",
            "address": "",
            "referred_by": "Website Quote Tool",
            "status": "New",
            "notes": notes
        }

        try:
            async with self._get_client() as client:
                url = self._build_url("/leads")

                response = await client.post(url, json=lead_data)
                response.raise_for_status()

                data = response.json()
                lead_id = str(data.get("lead", {}).get("id", ""))

                if lead_id:
                    logger.info(
                        f"Created Syncro lead {lead_id} for quote {quote.id}"
                    )
                    return LeadCreationResult(success=True, lead_id=lead_id)
                else:
                    logger.warning(
                        f"Syncro lead creation returned no ID for quote {quote.id}"
                    )
                    return LeadCreationResult(
                        success=False,
                        error="No lead ID returned from Syncro"
                    )

        except httpx.TimeoutException as e:
            error_msg = f"Syncro API timeout: {e}"
            logger.error(f"{error_msg} for quote {quote.id}")
            return LeadCreationResult(success=False, error=error_msg)

        except httpx.HTTPStatusError as e:
            error_msg = f"Syncro API error {e.response.status_code}: {e.response.text}"
            logger.error(f"{error_msg} for quote {quote.id}")
            return LeadCreationResult(success=False, error=error_msg)

        except Exception as e:
            error_msg = f"Unexpected error: {str(e)}"
            logger.error(f"{error_msg} for quote {quote.id}", exc_info=True)
            return LeadCreationResult(success=False, error=error_msg)

    def _parse_contact_name(self, full_name: str) -> tuple[str, str]:
        """
        Parse a full name into first and last name components.

        Args:
            full_name: Full contact name

        Returns:
            tuple: (first_name, last_name)
        """
        parts = full_name.strip().split(maxsplit=1)

        if len(parts) == 0:
            return ("", "")
        elif len(parts) == 1:
            return (parts[0], "")
        else:
            return (parts[0], parts[1])

    def _build_quote_summary(self, quote: "Quote") -> str:
        """
        Build formatted notes from quote items for Syncro lead.

        Creates a human-readable summary of the quote including:
        - Quote reference number
        - Monthly and setup totals
        - List of selected services with pricing
        - Customer notes if provided

        Args:
            quote: Quote object with items

        Returns:
            str: Formatted notes string for Syncro lead
        """
        lines = []

        # Quote reference
        access_token_short = quote.access_token[:8] if quote.access_token else "N/A"
        lines.append(f"Quote #{access_token_short}")
        lines.append("")

        # Totals
        monthly_total = quote.monthly_total or Decimal("0.00")
        setup_total = quote.setup_total or Decimal("0.00")

        lines.append(f"Monthly: ${monthly_total:,.2f}")
        if setup_total > 0:
            lines.append(f"Setup: ${setup_total:,.2f}")
        lines.append("")

        # Services
        lines.append("Services:")

        if hasattr(quote, 'items') and quote.items:
            for item in sorted(quote.items, key=lambda x: x.sort_order or 0):
                quantity = item.quantity or 1
                unit_price = item.unit_price or Decimal("0.00")
                line_total = quantity * unit_price

                if quantity > 1:
                    lines.append(
                        f"- {item.service_name} ({quantity} x ${unit_price:,.2f}): "
                        f"${line_total:,.2f}/mo"
                    )
                else:
                    lines.append(f"- {item.service_name}: ${unit_price:,.2f}/mo")

                # Add setup fee if present
                if item.setup_fee and item.setup_fee > 0:
                    lines.append(f"  Setup: ${item.setup_fee:,.2f}")
        else:
            lines.append("- No items")

        # Employee count
        if quote.employee_count:
            lines.append("")
            lines.append(f"Employees/Users: {quote.employee_count}")

        # Customer notes
        if quote.notes:
            lines.append("")
            lines.append("Customer Notes:")
            lines.append(quote.notes)

        return "\n".join(lines)


# Singleton instance for convenience
_syncro_service: Optional[SyncroService] = None


def get_syncro_service() -> SyncroService:
    """
    Get or create the singleton SyncroService instance.

    Returns:
        SyncroService: The service instance
    """
    global _syncro_service
    if _syncro_service is None:
        _syncro_service = SyncroService()
    return _syncro_service
