import logging
from typing import Optional, TypeVar, Dict, Any, Union

import httpx
from httpx import HTTPStatusError, Response
from httpx._types import RequestFiles
from pydantic import BaseModel

from .exceptions import PhrappyError
from .models import LoginDto
from .tags import (
    AdditionalWorkflowStepOperations,
    AnalysisOperations,
    AsyncRequestOperations,
    AuthenticationOperations,
    BilingualFileOperations,
    BusinessUnitOperations,
    BuyerOperations,
    ClientOperations,
    ConnectorOperations,
    CostCenterOperations,
    CustomFieldsOperations,
    CustomFileTypeOperations,
    DomainOperations,
    EmailTemplateOperations,
    FileOperations,
    GlossaryOperations,
    ImportsettingsOperations,
    ConversationsOperations,
    SupportedLanguagesOperations,
    LanguageQualityAssessmentOperations,
    QualityAssuranceOperations,
    MachineTranslationSettingsOperations,
    MachineTranslationOperations,
    MappingOperations,
    LanguageAIOperations,
    NetRateSchemeOperations,
    NotificationsOperations,
    PriceListOperations,
    ProjectTemplateOperations,
    TermBaseOperations,
    TranslationMemoryOperations,
    ProjectOperations,
    JobOperations,
    TranslationOperations,
    SegmentOperations,
    ProviderOperations,
    ProjectReferenceFileOperations,
    QuoteOperations,
    SCIMOperations,
    SegmentationRulesOperations,
    SpellCheckOperations,
    SubDomainOperations,
    UserOperations,
    WorkflowStepOperations,
    VendorOperations,
    WebhookOperations,
    XMLAssistantOperations,
    WorkflowchangesOperations
    
)


MEMSOURCE_BASE_URL = "https://cloud.memsource.com/web"

logger = logging.getLogger(__name__)

T = TypeVar('T', bound=BaseModel)


class Phrappy:
    """Client for interacting with the Phrase TMS API."""

    def __init__(self, token: Optional[str] = None, base_url: str = MEMSOURCE_BASE_URL):
        """Initialize the Phrase TMS client.
        
        Args:
            token: Optional API token. If provided without "ApiToken " prefix, it will be added.
            base_url: Base URL for API requests. Defaults to cloud.memsource.com.
        """
        self.base_url = base_url
        self.token = self._validate_token(token)
        self._init_operations()

    @staticmethod
    def _validate_token(token: Optional[str]) -> Optional[str]:
        """Validate and format the API token.
        
        Args:
            token: Raw API token string
            
        Returns:
            Properly formatted token or None
        """
        if not token:
            return None
        if token.startswith("ApiToken "):
            return token
        return f"ApiToken {token}"

    def _init_operations(self) -> None:
        """Initialize all API operation objects."""
        self.additional_workflow_step = AdditionalWorkflowStepOperations(self)
        self.analysis = AnalysisOperations(self)
        self.async_request = AsyncRequestOperations(self)
        self.authentication = AuthenticationOperations(self)
        self.bilingual_file = BilingualFileOperations(self)
        self.business_unit = BusinessUnitOperations(self)
        self.buyer = BuyerOperations(self)
        self.client = ClientOperations(self)
        self.connector = ConnectorOperations(self)
        self.cost_center = CostCenterOperations(self)
        self.custom_fields = CustomFieldsOperations(self)
        self.custom_file_type = CustomFileTypeOperations(self)
        self.domain = DomainOperations(self)
        self.email_template = EmailTemplateOperations(self)
        self.file = FileOperations(self)
        self.glossary = GlossaryOperations(self)
        self.importsettings = ImportsettingsOperations(self)
        self.conversations = ConversationsOperations(self)
        self.supported_languages = SupportedLanguagesOperations(self)
        self.language_quality_assessment = LanguageQualityAssessmentOperations(self)
        self.quality_assurance = QualityAssuranceOperations(self)
        self.machine_translation_settings = MachineTranslationSettingsOperations(self)
        self.machine_translation = MachineTranslationOperations(self)
        self.mapping = MappingOperations(self)
        self.language_ai = LanguageAIOperations(self)
        self.net_rate_scheme = NetRateSchemeOperations(self)
        self.notifications = NotificationsOperations(self)
        self.price_list = PriceListOperations(self)
        self.project_template = ProjectTemplateOperations(self)
        self.term_base = TermBaseOperations(self)
        self.translation_memory = TranslationMemoryOperations(self)
        self.project = ProjectOperations(self)
        self.job = JobOperations(self)
        self.translation = TranslationOperations(self)
        self.segment = SegmentOperations(self)
        self.provider = ProviderOperations(self)
        self.project_reference_file = ProjectReferenceFileOperations(self)
        self.quote = QuoteOperations(self)
        self.scim = SCIMOperations(self)
        self.segmentation_rules = SegmentationRulesOperations(self)
        self.spell_check = SpellCheckOperations(self)
        self.sub_domain = SubDomainOperations(self)
        self.user = UserOperations(self)
        self.workflow_step = WorkflowStepOperations(self)
        self.vendor = VendorOperations(self)
        self.webhook = WebhookOperations(self)
        self.xml_assistant = XMLAssistantOperations(self)
        self.workflowchanges = WorkflowchangesOperations(self)
        self._last_response: Optional[Response] = None

    def make_request(
        self,
        method: str,
        path: str,
        phrase_token: Optional[str] = None,
        params: Optional[Dict[str, Any]] = None,
        payload: Optional[Union[T, Dict[str, Any]]] = None,
        files: Optional[RequestFiles] = None,
        headers: Optional[Dict[str, str]] = None,
        content: Optional[bytes] = None,
        timeout: float = 180.0
    ) -> Response:
        """Make an HTTP request to the Phrase TMS API.

        Args:
            method: HTTP method (GET, POST, etc)
            path: API endpoint path
            phrase_token: Optional token override
            params: Optional URL parameters
            payload: Optional request body
            files: Optional files to upload
            headers: Optional HTTP headers
            content: Optional raw content body
            timeout: Request timeout in seconds

        Returns:
            httpx.Response object

        Raises:
            PhrappyError: If the API request fails
        """
        # Build request URL and headers
        if not path.startswith('/'):
            path = '/' + path
        url = self.base_url.rstrip('/') + path
        request_headers = {}
        if token := (phrase_token or self.token):
            request_headers["Authorization"] = token
        if headers:
            request_headers.update(headers)

        # Convert Pydantic models to dicts
        if payload is not None and not isinstance(payload, dict):
            try:
                payload = payload.model_dump(exclude_unset=True)
            except Exception as e:
                logger.exception("Failed to serialize payload")
                raise PhrappyError("Failed to serialize request payload") from e

        # Clean up None values from params
        if params:
            params = {k: v for k, v in params.items() if v is not None}
        response = None

        try:
            response = httpx.request(
                method=method,
                url=url,
                headers=request_headers,
                params=params,
                json=payload,
                files=files,
                content=content,
                timeout=timeout,
            )
            self._last_response = response
            response.raise_for_status()
            return response

        except HTTPStatusError as exc:
            if response:
                self._last_response = response
                try:
                    error_data = response.json()
                    error_code = error_data.get("errorCode")
                    error_desc = error_data.get("errorDescription")
                    msg = (
                        f"API request failed: {method} {url}\n"
                        f"Status: {response.status_code}\n"
                        f"Error code: {error_code}\n"
                        f"Description: {error_desc}"
                    )
                    raise PhrappyError(msg) from exc
                except ValueError:
                    raise PhrappyError(
                        f"API request failed with invalid JSON response: {response.text}"
                    ) from exc
            else:
                raise PhrappyError(f"Request failed with no response: {str(exc)}") from exc

        except Exception as e:
            logger.exception(f"Unexpected error during request: {url}")
            raise PhrappyError(f"An unexpected error occurred: {str(e)}") from e

    @classmethod
    def from_creds(cls, username: str, password: str) -> 'Phrappy':
        pp = cls()
        login_dto = {"userName": username, "password": password}
        resp = pp.authentication.login(login_dto)
        return cls(resp.token)
