"""
Meta Store API Client - Handles all interactions with the metadata store API
Replaces direct database access with REST API calls
"""

import os
from typing import List, Optional, Dict, Any
import requests
import re


def snake_to_camel(snake_str: str) -> str:
    """Convert snake_case to camelCase"""
    components = snake_str.split("_")
    return components[0] + "".join(x.title() for x in components[1:])


def camel_to_snake(camel_str: str) -> str:
    """Convert camelCase to snake_case"""
    return re.sub(r"(?<!^)(?=[A-Z])", "_", camel_str).lower()


def convert_dict_keys(data: Dict[str, Any], converter_func) -> Dict[str, Any]:
    """
    Recursively convert dictionary keys using the provided converter function

    Args:
        data: Dictionary to convert
        converter_func: Function to convert keys (snake_to_camel or camel_to_snake)

    Returns:
        Dictionary with converted keys
    """
    if not isinstance(data, dict):
        return data

    converted = {}
    for key, value in data.items():
        new_key = converter_func(key)

        if isinstance(value, dict):
            converted[new_key] = convert_dict_keys(value, converter_func)
        elif isinstance(value, list):
            converted[new_key] = [
                (
                    convert_dict_keys(item, converter_func)
                    if isinstance(item, dict)
                    else item
                )
                for item in value
            ]
        else:
            converted[new_key] = value

    return converted


class MetaStoreClient:
    """Client for interacting with the UDA Metadata Store API"""

    def __init__(self, base_url: Optional[str] = None, timeout: int = 30):
        """
        Initialize the MetaStore API client

        Args:
            base_url: Base URL for the API (default: from env or localhost)
            timeout: Request timeout in seconds
        """
        self.base_url = base_url or os.getenv("UDA_BASE_URL", "http://localhost:8080")
        self.timeout = timeout
        self.session = requests.Session()

        # Set default headers
        self.session.headers.update(
            {"Content-Type": "application/json", "Accept": "application/json"}
        )

    def _make_request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
        """
        Make an HTTP request with error handling

        Args:
            method: HTTP method (GET)
            endpoint: API endpoint path
            **kwargs: Additional arguments for requests

        Returns:
            Response object

        Raises:
            requests.RequestException: On request failure
        """
        url = f"{self.base_url}{endpoint}"
        kwargs.setdefault("timeout", self.timeout)

        # Convert request body keys to camelCase if json data is provided
        if "json" in kwargs and kwargs["json"] is not None:
            kwargs["json"] = convert_dict_keys(kwargs["json"], snake_to_camel)

        try:
            response = self.session.request(method, url, **kwargs)
            response.raise_for_status()
            return response
        except requests.exceptions.HTTPError as e:
            # Re-raise with more context
            raise Exception(
                f"API request failed [{response.status_code}]: {response.text}"
            ) from e
        except requests.exceptions.RequestException as e:
            raise Exception(f"API request failed: {str(e)}") from e

    def _convert_response(self, response_data: Any) -> Any:
        """
        Convert API response from camelCase to snake_case

        Args:
            response_data: Response data from API

        Returns:
            Data with snake_case keys
        """
        if isinstance(response_data, dict):
            return convert_dict_keys(response_data, camel_to_snake)
        elif isinstance(response_data, list):
            return [
                (
                    convert_dict_keys(item, camel_to_snake)
                    if isinstance(item, dict)
                    else item
                )
                for item in response_data
            ]
        return response_data

    # ==================== Downloader Operations ====================

    def get_downloaders(self, page_size: int = 50, page: int = 1) -> Dict[str, Any]:
        """
        Get list of downloaders with pagination

        Args:
            page_size: Number of results per page
            page: Page number

        Returns:
            Dict containing downloaders list and pagination info
        """
        params = {"pageSize": page_size, "page": page}
        response = self._make_request("GET", "/downloaders", params=params)
        return self._convert_response(response.json())

    def get_downloader_by_id(self, downloader_id: str) -> Optional[Dict[str, Any]]:
        """
        Get a specific downloader by ID

        Args:
            downloader_id: Downloader ID

        Returns:
            Downloader dict or None if not found
        """
        try:
            response = self._make_request(
                "GET", f"/downloaders/{downloader_id}/detailed"
            )
            return self._convert_response(response.json())
        except Exception as e:
            if "404" in str(e):
                return None
            raise

    def get_all_downloaders(self) -> List[Dict[str, Any]]:
        """
        Get all downloaders (handles pagination automatically)
        Matches the get_all_downloaders(conn) signature from db operations

        Returns:
            List of all downloader dicts
        """
        all_downloaders = []
        page = 1
        page_size = 100

        while True:
            result = self.get_downloaders(page_size=page_size, page=page)
            downloaders = result.get("data", result.get("items", []))

            if not downloaders:
                break

            all_downloaders.extend(downloaders)

            # Check if there are more pages
            total = result.get("total", len(all_downloaders))
            if len(all_downloaders) >= total:
                break

            page += 1

        return all_downloaders

    # ==================== Download Pattern Operations ====================

    def get_download_patterns(
        self, downloader_id: Optional[str] = None, page_size: int = 50
    ) -> Dict[str, Any]:
        """
        Get download patterns, optionally filtered by downloader

        Args:
            downloader_id: Filter by downloader ID
            page_size: Number of results per page

        Returns:
            Dict containing patterns list
        """
        params = {"pageSize": page_size}
        if downloader_id:
            params["downloaderId"] = downloader_id

        response = self._make_request("GET", "/download-patterns", params=params)
        return response.json()

    def get_patterns_by_downloader(self, downloader_id: str) -> List[Dict[str, Any]]:
        """
        Get all download patterns for a specific downloader
        Matches the get_patterns_by_downloader(conn, downloader_id) signature from db operations

        Args:
            downloader_id: Downloader ID

        Returns:
            List of pattern dicts
        """
        result = self.get_download_patterns(downloader_id=downloader_id, page_size=1000)
        return result.get("data", result.get("patterns", []))

    # ==================== Validation Rules Operations ====================

    def get_validation_rules(
        self, download_pattern_id: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        Get validation rules, optionally filtered by download pattern

        Args:
            download_pattern_id: Filter by download pattern ID

        Returns:
            Dict containing validation rules list
        """
        params = {}
        if download_pattern_id:
            params["downloadPatternId"] = download_pattern_id

        response = self._make_request("GET", "/validation-rules", params=params)
        return self._convert_response(response.json())

    def get_validation_rules_by_pattern(
        self, download_pattern_id: str
    ) -> List[Dict[str, Any]]:
        """
        Get all validation rules for a specific download pattern
        Matches the get_validation_rules_by_pattern(conn, download_pattern_id) signature

        Args:
            download_pattern_id: Download pattern ID

        Returns:
            List of validation rule dicts
        """
        result = self.get_validation_rules(download_pattern_id=download_pattern_id)
        return result.get("data", result.get("rules", []))

    # ==================== Utility Methods ====================

    def health_check(self) -> bool:
        """
        Check if the API is healthy and reachable

        Returns:
            True if API is healthy
        """
        try:
            response = self._make_request("GET", "/health")
            return response.status_code == 200
        except Exception:
            return False

    def close(self):
        """Close the session"""
        self.session.close()

    def __enter__(self):
        """Context manager entry"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit"""
        self.close()


# ============================================================================
# Factory function for backward compatibility
# ============================================================================


def get_meta_store_client(base_url: Optional[str] = None) -> MetaStoreClient:
    """
    Factory function to create a MetaStoreClient instance

    Args:
        base_url: Base URL for the API

    Returns:
        MetaStoreClient instance
    """
    return MetaStoreClient(base_url=base_url)
