"""
Figma API wrapper for design token extraction
Based on Figmagic architecture and W3C DTCG format standards
"""

import json
import requests
from pathlib import Path
from typing import Dict, List, Optional, Any
from dss.models.theme import Theme, DesignToken, TokenCategory


class FigmaWrapper:
    """
    Wrapper for Figma REST API
    Extracts design tokens from Figma Variables and converts to W3C DTCG format

    Architecture:
    Figma Variables API → W3C DTCG format → DSS Theme model → StyleDictionary → outputs
    """

    FIGMA_API_BASE = "https://api.figma.com/v1"

    def __init__(self, api_token: str, file_key: str, use_cache: bool = True):
        """
        Initialize Figma wrapper

        Args:
            api_token: Figma personal access token
            file_key: Figma file key (from URL)
            use_cache: Whether to cache API responses
        """
        if not api_token or not file_key:
            raise ValueError("Figma API token and file key are required")

        self.api_token = api_token
        self.file_key = file_key
        self.use_cache = use_cache
        self.cache_path = Path.home() / ".dss" / "figma_cache.json"

        self.headers = {
            "X-Figma-Token": self.api_token
        }

    def get_variables(self) -> Dict[str, Any]:
        """
        Fetch variables from Figma file using Variables API

        Returns:
            Raw Figma Variables API response

        Raises:
            FigmaAPIError: If API request fails
        """
        # Check cache first
        if self.use_cache and self.cache_path.exists():
            with open(self.cache_path, 'r') as f:
                return json.load(f)

        # Fetch from API
        url = f"{self.FIGMA_API_BASE}/files/{self.file_key}/variables/local"

        try:
            response = requests.get(url, headers=self.headers)
            response.raise_for_status()

            data = response.json()

            # Cache response
            if self.use_cache:
                self.cache_path.parent.mkdir(parents=True, exist_ok=True)
                with open(self.cache_path, 'w') as f:
                    json.dump(data, f, indent=2)

            return data

        except requests.exceptions.HTTPError as e:
            if e.response.status_code == 403:
                raise FigmaAPIError("Invalid Figma API token (403 Forbidden)")
            elif e.response.status_code == 404:
                raise FigmaAPIError(f"Figma file '{self.file_key}' not found (404)")
            else:
                raise FigmaAPIError(f"Figma API error: {e}")
        except Exception as e:
            raise FigmaAPIError(f"Failed to fetch Figma variables: {e}")

    def extract_themes(self) -> Dict[str, Theme]:
        """
        Extract themes from Figma Variables

        Figma uses "variable collections" with "modes" for themes.
        Example: Collection "Colors" might have modes "Light" and "Dark"

        Returns:
            Dict mapping theme name to DSS Theme object
        """
        figma_data = self.get_variables()

        # Build mode ID → theme name mapping
        mode_map = self._build_mode_map(figma_data.get("meta", {}).get("variableCollections", {}))

        # Extract variables and convert to DTCG format
        variables = figma_data.get("meta", {}).get("variables", {})
        dtcg_tokens = self._convert_to_dtcg(variables)

        # Structure tokens by theme
        themes = self._structure_by_theme(dtcg_tokens, mode_map)

        return themes

    def _build_mode_map(self, variable_collections: Dict[str, Any]) -> Dict[str, str]:
        """
        Build mapping of mode ID → theme name

        Args:
            variable_collections: Figma variable collections data

        Returns:
            Dict mapping mode ID to theme name (e.g., {"331:7": "Light"})
        """
        mode_map = {}

        for collection_id, collection in variable_collections.items():
            modes = collection.get("modes", [])
            for mode in modes:
                mode_id = mode.get("modeId")
                mode_name = mode.get("name")
                if mode_id and mode_name:
                    mode_map[mode_id] = mode_name

        return mode_map

    def _convert_to_dtcg(self, variables: Dict[str, Any]) -> Dict[str, Any]:
        """
        Convert Figma variables to W3C DTCG format

        DTCG format:
        {
          "color": {
            "primary": {
              "$value": "#0066cc",
              "$type": "color",
              "$description": "Primary brand color"
            }
          }
        }

        Args:
            variables: Figma variables data

        Returns:
            DTCG-formatted token tree
        """
        tokens = {}

        for var_id, variable in variables.items():
            # Skip remote variables
            if variable.get("remote", False):
                continue

            name = variable.get("name", "")
            resolved_type = variable.get("resolvedType", "")
            values_by_mode = variable.get("valuesByMode", {})
            description = variable.get("description", "")

            # Convert Figma type to DTCG type
            dtcg_type = self._map_figma_type_to_dtcg(resolved_type)

            # Parse name into nested structure (e.g., "colors/primary/500" → colors.primary.500)
            path_parts = name.split("/")

            # Create token object with values by mode
            token_obj = {
                "$type": dtcg_type,
                "valuesByMode": values_by_mode
            }

            if description:
                token_obj["$description"] = description

            # Set in nested structure
            self._set_nested(tokens, path_parts, token_obj)

        return tokens

    def _structure_by_theme(self, dtcg_tokens: Dict[str, Any], mode_map: Dict[str, str]) -> Dict[str, Theme]:
        """
        Structure tokens by theme using mode mapping

        Args:
            dtcg_tokens: DTCG tokens with valuesByMode
            mode_map: Mapping of mode ID to theme name

        Returns:
            Dict of theme name → DSS Theme
        """
        themes = {}

        # Initialize themes
        for mode_name in set(mode_map.values()):
            themes[mode_name] = Theme(
                name=f"DSS {mode_name}",
                version="1.0.0",
                tokens={}
            )

        # Recursively extract tokens for each theme
        def extract_tokens(node: Dict[str, Any], path: str = ""):
            for key, value in node.items():
                if key.startswith("$"):
                    # Skip metadata keys
                    continue

                current_path = f"{path}/{key}" if path else key

                if isinstance(value, dict) and "valuesByMode" in value:
                    # This is a token leaf node
                    dtcg_type = value.get("$type", "other")
                    description = value.get("$description", "")
                    values_by_mode = value["valuesByMode"]

                    # Create token for each mode
                    for mode_id, token_value in values_by_mode.items():
                        theme_name = mode_map.get(mode_id)
                        if theme_name and theme_name in themes:
                            # Convert path to token name (use last part for simplicity)
                            token_name = key

                            # Format value based on type
                            formatted_value = self._format_value(token_value, dtcg_type)

                            # Map DTCG type to DSS TokenCategory
                            category = self._map_dtcg_type_to_category(dtcg_type)

                            # Create DesignToken
                            design_token = DesignToken(
                                name=token_name,
                                value=formatted_value,
                                type=dtcg_type,
                                category=category,
                                description=description or f"{token_name} token"
                            )

                            themes[theme_name].tokens[token_name] = design_token

                elif isinstance(value, dict):
                    # Recurse into nested groups
                    extract_tokens(value, current_path)

        extract_tokens(dtcg_tokens)

        return themes

    def _map_figma_type_to_dtcg(self, figma_type: str) -> str:
        """Map Figma variable type to W3C DTCG type"""
        type_map = {
            "COLOR": "color",
            "FLOAT": "number",  # Could be dimension, duration, etc.
            "STRING": "string",
            "BOOLEAN": "boolean"
        }
        return type_map.get(figma_type, "other")

    def _map_dtcg_type_to_category(self, dtcg_type: str) -> TokenCategory:
        """Map DTCG type to DSS TokenCategory"""
        category_map = {
            "color": TokenCategory.COLOR,
            "dimension": TokenCategory.SPACING,
            "fontFamily": TokenCategory.TYPOGRAPHY,
            "fontSize": TokenCategory.TYPOGRAPHY,
            "fontWeight": TokenCategory.TYPOGRAPHY,
            "lineHeight": TokenCategory.TYPOGRAPHY,
            "borderRadius": TokenCategory.RADIUS,
            "shadow": TokenCategory.SHADOW,
            "border": TokenCategory.BORDER,
        }
        return category_map.get(dtcg_type, TokenCategory.OTHER)

    def _format_value(self, value: Any, dtcg_type: str) -> str:
        """
        Format Figma value to string representation

        Args:
            value: Raw Figma value
            dtcg_type: DTCG type

        Returns:
            Formatted value string
        """
        if dtcg_type == "color" and isinstance(value, dict):
            # Figma color format: {r: 0-1, g: 0-1, b: 0-1, a: 0-1}
            r = int(value.get("r", 0) * 255)
            g = int(value.get("g", 0) * 255)
            b = int(value.get("b", 0) * 255)
            a = value.get("a", 1)

            if a == 1:
                return f"rgb({r}, {g}, {b})"
            else:
                return f"rgba({r}, {g}, {b}, {a})"

        return str(value)

    def _set_nested(self, obj: Dict, path: List[str], value: Any):
        """Set value in nested dictionary using path"""
        current = obj
        for part in path[:-1]:
            if part not in current:
                current[part] = {}
            current = current[part]
        current[path[-1]] = value


class FigmaAPIError(Exception):
    """Exception raised for Figma API errors"""
    pass
