# This file was auto-generated by Fern from our API Definition.

import typing
import copy
import datetime
import json
import datetime
import json
import logging
import random
import string


import volcenginesdkvefaas
import volcenginesdkcore

from .base import BaseProvider
from ..types.sandbox_response import SandboxResponse
from .sign import request

logger = logging.getLogger(__name__)


class VolcengineProvider(BaseProvider):
    """
    Volcengine cloud provider implementation for sandbox management.
    
    This provider uses the Volcengine VEFAAS (Volcengine Function as a Service)
    API to manage sandbox instances.
    """
    
    def __init__(
        self,
        access_key: str,
        secret_key: str,
        region: str = "cn-beijing",
        client_side_validation: bool = True
    ):
        """
        Initialize the Volcengine provider.
        
        Parameters
        ----------
        access_key : str
            Volcengine access key ID
        secret_key : str
            Volcengine secret access key
        region : str, optional
            Volcengine region, defaults to "cn-beijing"
        client_side_validation : bool, optional
            Enable client-side validation, defaults to True
        """
        
        self.access_key = access_key
        self.secret_key = secret_key
        self.region = region
        self.client_side_validation = client_side_validation
        
        # Initialize Volcengine configuration
        configuration = volcenginesdkcore.Configuration()
        configuration.ak = access_key
        configuration.sk = secret_key
        configuration.region = region
        configuration.client_side_validation = client_side_validation
        volcenginesdkcore.Configuration.set_default(configuration)

        # Initialize VEFAAS client
        self._client = volcenginesdkvefaas.VEFAASApi(volcenginesdkcore.ApiClient(configuration))
    
    def create_sandbox(
        self,
        function_id: str,
        timeout: int = 30,
        **kwargs
    ) -> SandboxResponse:
        """
        Create a new sandbox instance using Volcengine VEFAAS.
        
        Parameters
        ----------
        function_id : str
            The function ID for the sandbox
        timeout: int
            The timeout for the sandbox creation in minutes
        **kwargs
            Additional parameters for sandbox creation
            
        Returns
        -------
        str
            The ID of the created sandbox
        Exception
            The exception raised if the sandbox creation fails
        -------
        """
        try:
            request = volcenginesdkvefaas.CreateSandboxRequest(
                function_id=function_id,
                timeout=timeout,
                **kwargs
            )
            response = self._client.create_sandbox(request)
            return response.sandbox_id
            
        except Exception as e:
            return e
    
    def delete_sandbox(self, function_id: str, sandbox_id: str, **kwargs) -> SandboxResponse:
        """
        Delete an existing sandbox instance.
        
        Parameters
        ----------
        function_id : str
            The function ID of the sandbox
        sandbox_id : str
            The ID of the sandbox to delete
        **kwargs
            Additional parameters for sandbox deletion
            
        Returns
        -------
        SandboxResponse
            The response containing deletion status
        """
        try:
            request = volcenginesdkvefaas.KillSandboxRequest(
                function_id=function_id,
                sandbox_id=sandbox_id,
                **kwargs
            )
            response = self._client.kill_sandbox(request)

            return response
        
        except Exception as e:
            return e
    
    def _append_instance_query_struct(self, domains_info: typing.List[typing.Dict[str, typing.Any]], instance_name: str) -> typing.List[typing.Dict[str, typing.Any]]:
        """Append ?faasInstanceName= to domain field of structured domain objects, preserving type."""
        result: typing.List[typing.Dict[str, typing.Any]] = []
        for info in domains_info:
            domain_str = info.get("domain") or info.get("Domain") or ""
            if not domain_str:
                continue
            if "?" in domain_str:
                new_domain = f"{domain_str}&faasInstanceName={instance_name}"
            else:
                new_domain = f"{domain_str}?faasInstanceName={instance_name}"
            result.append({
                "domain": new_domain,
                "type": info.get("type") or info.get("Type"),
            })
        return result
    
    def get_sandbox(self, function_id: str, sandbox_id: str, **kwargs) -> SandboxResponse:
        """
        Get details of an existing sandbox instance.
        
        Parameters
        ----------
        function_id : str
            The function ID of the sandbox
        sandbox_id : str
            The ID of the sandbox to retrieve
        **kwargs
            Additional parameters for sandbox retrieval
            
        Returns
        -------
        SandboxResponse
            The response containing sandbox details
        """
        try:
            request = volcenginesdkvefaas.DescribeSandboxRequest(
                function_id=function_id,
                sandbox_id=sandbox_id,
                **kwargs
            )
            response = self._client.describe_sandbox(request)

            out = response.to_dict()
            base_domains = self.get_apig_domains(function_id)
            domains_struct = self._append_instance_query_struct(base_domains, sandbox_id)
            out["domains"] = domains_struct
            return out
            
        except Exception as e:
            return e
    
    def list_sandboxes(self, function_id: str, **kwargs) -> SandboxResponse:
        """
        List all sandbox instances for a function.
        
        Parameters
        ----------
        function_id : str
            The function ID to list sandboxes for
        **kwargs
            Additional parameters for listing sandboxes
            
        Returns
        -------
        SandboxResponse
            The response containing list of sandboxes
        """
        try:
            request = volcenginesdkvefaas.ListSandboxesRequest(
                function_id=function_id,
                **kwargs
            )
            response = self._client.list_sandboxes(request)
            
            # Attach domains with instanceName query to each sandbox item using instance_id
            base_domains = self.get_apig_domains(function_id)

            sandboxes = response.sandboxes
            normalized: typing.List[typing.Dict[str, typing.Any]] = []
            for sb in sandboxes:
                item = sb.to_dict()
                instance_id = item.get("id") or item.get("sandbox_id")
                domains_struct = self._append_instance_query_struct(base_domains, instance_id) if instance_id else base_domains
                item["domains"] = domains_struct
                normalized.append(item)

            return normalized
            
        except Exception as e:
            return e


    def _get_apig_trigger(self, function_id: str) -> str:
        """
        Get the UpstreamId from APIG triggers for a given function.
        
        Parameters
        ----------
        function_id : str
            The function ID to get triggers for
            
        Returns
        -------
        str
            The UpstreamId from the first APIG trigger found, or None if not found
        """
        body = {
            "FunctionId": function_id,
        }
        now = datetime.datetime.utcnow()
        response = request("POST", now, {}, {}, self.access_key, self.secret_key,"", "ListTriggers", json.dumps(body))
        
        # Parse the response to extract UpstreamId from APIG triggers
        if response and isinstance(response, dict):
            result = response.get("Result", {})
            items = result.get("Items", [])
            
            for item in items:
                if item.get("Type") == "apig":
                    detailed_config = item.get("DetailedConfig", "{}")
                    try:
                        config = json.loads(detailed_config)
                        upstream_id = config.get("UpstreamId")
                        if upstream_id:
                            return upstream_id
                    except json.JSONDecodeError:
                        print(f"Failed to parse DetailedConfig: {detailed_config}")
                        continue
        
        return None

    def _get_apig_domains(self, upstream_id: str) -> list:
        """
        Get structured domains from APIG routes using the upstream ID.
        
        Parameters
        ----------
        upstream_id : str
            The upstream ID to get routes for
            
        Returns
        -------
        list
            List of domains from the routes, or empty list if not found
        """
        body = {
            "UpstreamId": upstream_id,
            "PageSize": 100,
            "PageNumber": 1
        }
        
        now = datetime.datetime.utcnow()
        response = request("POST", now, {}, {}, self.access_key, self.secret_key, "", "ListRoutes", json.dumps(body))
                
        # Extract domains from the response and append MatchContent path, preserving type
        domains: typing.List[typing.Dict[str, typing.Any]] = []
        if response and isinstance(response, dict):
            result = response.get("Result", {})
            routes = result.get("Items", [])
            for route in routes:
                # derive path prefix from match rule
                path_prefix = ""
                try:
                    match_rule = route.get("MatchRule") or {}
                    path_rule = match_rule.get("Path") or {}
                    match_content = path_rule.get("MatchContent")
                    if isinstance(match_content, str):
                        path_prefix = match_content
                except Exception:
                    path_prefix = ""

                route_domains = route.get("Domains", [])
                for domain_info in route_domains:
                    base = domain_info.get("Domain")
                    if not base:
                        continue
                    # Append path prefix (e.g., "/v1") to the domain string, and preserve type
                    domains.append({
                        "domain": f"{base}{path_prefix}",
                        "type": domain_info.get("Type") or domain_info.get("type"),
                    })
        
        return domains

    def get_apig_domains(self, function_id: str) -> list:
        """
        Get domains for APIG triggers of a given function.
        
        Parameters
        ----------
        function_id : str
            The function ID to get domains for
            
        Returns
        -------
        list
            List of domains from APIG routes, or empty list if not found
        """
        upstream_id = self._get_apig_trigger(function_id)
        if upstream_id:
            return self._get_apig_domains(upstream_id)
        return []

    def _create_application(self, name: str, gateway_name: str, **kwargs):
        """
        Create an application using Volcengine VEFAAS.
        
        Parameters
        ----------
        **kwargs
            Additional parameters for application creation
            
        Returns
        -------
        SandboxResponse
            The response containing application creation details
        """

        function_name = f"{name}-function"

        sid = "".join(random.choices(string.digits, k=7))

        body = {
            "Name": name,
            "Config": {
                "region": "cn-beijing",
                "functionName": function_name,
                "gatewayName": gateway_name,
                "sid": sid,
            },
            "TemplateId": "68ad2fb0443cb8000843cbbe",
        }

        now = datetime.datetime.utcnow()
        try:
            response = request(
                "POST",
                now,
                {},
                {},
                self.access_key,
                self.secret_key,
                "",
                "CreateApplication",
                json.dumps(body),
            )
        except Exception:
            logger.exception("CreateApplication request failed")
            return None

        if not isinstance(response, dict):
            logger.error("CreateApplication returned non-dict response: %s", response)
            return None

        result = response.get("Result")
        if not isinstance(result, dict):
            logger.error("CreateApplication response missing Result: %s", response)
            return None

        application_id = result.get("Id")
        if not application_id:
            logger.error("CreateApplication response missing Result.Id: %s", response)
            return None

        return application_id
    
    def _release_application(self, id: str, **kwargs):
        """
        Delete an application using Volcengine VEFAAS.
        """
        body = {"Id":id}
        now = datetime.datetime.utcnow()
        response = request("POST", now, {}, {}, self.access_key, self.secret_key, "", "ReleaseApplication", json.dumps(body))
        return response

    def create_application(self, name: str, gateway_name: str, **kwargs):
        """
        Create an application using Volcengine VEFAAS.
        """
        if not name:
            raise ValueError("name is required to create an application")
        if not gateway_name:
            raise ValueError("gateway_name is required to create an application")

        application_id = self._create_application(name, gateway_name, **kwargs)
        if not application_id:
            return None

        try:
            self._release_application(application_id, **kwargs)
        except Exception:
            logger.exception("ReleaseApplication request failed for id %s", application_id)

        return application_id

    def get_application_readiness(self, id: str, **kwargs) -> tuple[bool, typing.Optional[str]]:
        """Return readiness flag and function ID when available."""

        body = {"Id": id}
        now = datetime.datetime.utcnow()
        try:
            response = request(
                "POST",
                now,
                {},
                {},
                self.access_key,
                self.secret_key,
                "",
                "GetApplication",
                json.dumps(body),
            )
        except Exception:
            logger.exception("GetApplication request failed for id %s", id)
            return False, None

        if not isinstance(response, dict):
            logger.error("GetApplication returned non-dict response: %s", response)
            return False, None

        result = response.get("Result")
        if not isinstance(result, dict):
            logger.error("GetApplication response missing Result: %s", response)
            return False, None

        function_id: typing.Optional[str] = None
        cloud_resource_raw = result.get("CloudResource")
        if isinstance(cloud_resource_raw, str):
            try:
                cloud_resource = json.loads(cloud_resource_raw)
            except json.JSONDecodeError:
                logger.exception("Failed to decode CloudResource for application %s", id)
            else:
                if isinstance(cloud_resource, dict):
                    function_id = cloud_resource.get("function_id")
                    if not function_id:
                        sandbox_info = cloud_resource.get("sandbox")
                        if isinstance(sandbox_info, dict):
                            function_id = sandbox_info.get("function_id")
        elif isinstance(cloud_resource_raw, dict):
            function_id = cloud_resource_raw.get("function_id")

        status = result.get("Status")
        is_ready = status == "deploy_success"
        if not is_ready:
            logger.info("Application %s not ready. Status: %s", id, status)
            return False, function_id

        return True, function_id
        
