"""
Domain exceptions for Moy Nalog API.
Mirrors PHP library's exception hierarchy and error handling.
"""

import logging
import re
from http import HTTPStatus

import httpx

logger = logging.getLogger(__name__)


class DomainException(Exception):  # noqa: N818 для совместимости публичного API
    """Base domain exception for all Moy Nalog API errors."""

    def __init__(self, message: str, response: httpx.Response | None = None):
        super().__init__(message)
        self.response = response

        # Log the error with response details (without sensitive data)
        if response:
            self._log_error_details(message, response)

    def _log_error_details(self, message: str, response: httpx.Response) -> None:
        """Log error details while avoiding sensitive information."""
        # Mask potential sensitive data in URLs and headers
        safe_url = self._mask_sensitive_url(str(response.url))
        safe_headers = self._mask_sensitive_headers(dict(response.headers))

        logger.error(
            'API Error: %s | Status: %d | URL: %s | Headers: %s | Body: %s',
            message,
            response.status_code,
            safe_url,
            safe_headers,
            self._get_safe_response_body(response),
        )

    def _mask_sensitive_url(self, url: str) -> str:
        """Mask potential sensitive data in URL."""
        # Replace tokens/keys with asterisks
        patterns = [
            (r'(token=)[^&]*', r'\1***'),
            (r'(key=)[^&]*', r'\1***'),
            (r'(secret=)[^&]*', r'\1***'),
        ]

        for pattern, replacement in patterns:
            url = re.sub(pattern, replacement, url)
        return url

    def _mask_sensitive_headers(self, headers: dict[str, str]) -> dict[str, str]:
        """Mask sensitive headers."""
        safe_headers = headers.copy()
        sensitive_keys = ['authorization', 'x-api-key', 'cookie', 'set-cookie']

        for key in sensitive_keys:
            if key.lower() in [h.lower() for h in safe_headers]:
                # Find the actual key (case-insensitive)
                actual_key = next(k for k in safe_headers if k.lower() == key.lower())
                safe_headers[actual_key] = '***'

        return safe_headers

    def _get_safe_response_body(self, response: httpx.Response) -> str:
        """Get response body with potential sensitive data masked."""
        try:
            body = response.text[:1000]  # Limit body size for logging
            # Mask potential tokens in JSON responses
            patterns = [
                (r'("token":\s*")[^"]*(")', r'\1***\2'),
                (r'("refreshToken":\s*")[^"]*(")', r'\1***\2'),
                (r'("password":\s*")[^"]*(")', r'\1***\2'),
                (r'("secret":\s*")[^"]*(")', r'\1***\2'),
            ]

            for pattern, replacement in patterns:
                body = re.sub(pattern, replacement, body)

            return body
        except Exception:
            return '[Failed to read response body]'


class ValidationException(DomainException):
    """HTTP 400 - Validation error."""


class UnauthorizedException(DomainException):
    """HTTP 401 - Authentication required or invalid credentials."""


class ForbiddenException(DomainException):
    """HTTP 403 - Access forbidden."""


class NotFoundException(DomainException):
    """HTTP 404 - Resource not found."""


class ClientException(DomainException):
    """HTTP 406 - Client error (e.g., wrong Accept headers)."""


class PhoneException(DomainException):
    """HTTP 422 - Phone-related error (SMS, verification, etc.)."""


class ServerException(DomainException):
    """HTTP 500 - Internal server error."""


class UnknownErrorException(DomainException):
    """Unknown HTTP error code."""


def raise_for_status(response: httpx.Response) -> None:
    """
    Raise appropriate domain exception based on HTTP status code.

    Maps status codes to exceptions exactly like PHP ErrorHandler:
    - 400: ValidationException
    - 401: UnauthorizedException
    - 403: ForbiddenException
    - 404: NotFoundException
    - 406: ClientException
    - 422: PhoneException
    - 500: ServerException
    - default: UnknownErrorException

    Args:
        response: httpx.Response object

    Raises:
        DomainException: Appropriate exception for status code
    """
    if response.status_code < HTTPStatus.BAD_REQUEST:
        return

    body = response.text

    if response.status_code == HTTPStatus.BAD_REQUEST:
        raise ValidationException(body, response)
    if response.status_code == HTTPStatus.UNAUTHORIZED:
        raise UnauthorizedException(body, response)
    if response.status_code == HTTPStatus.FORBIDDEN:
        raise ForbiddenException(body, response)
    if response.status_code == HTTPStatus.NOT_FOUND:
        raise NotFoundException(body, response)
    if response.status_code == HTTPStatus.NOT_ACCEPTABLE:
        raise ClientException('Wrong Accept headers', response)
    if response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
        raise PhoneException(body, response)
    if response.status_code == HTTPStatus.INTERNAL_SERVER_ERROR:
        raise ServerException(body, response)
    raise UnknownErrorException(body, response)
