"""
Integration client for API operations.

This module provides the IntegrationClient class for interacting with
Integration (ToolPlugin) v3 API endpoints.
"""

from typing import Optional
from pygeai import logger
from pygeai.core.utils.validators import validate_status_code
from pygeai.core.utils.parsers import parse_json_response
from pygeai.core.common.exceptions import InvalidAPIResponseException
from pygeai.lab.integrations.endpoints import (
    LIST_INTEGRATIONS_V3,
    GET_INTEGRATION_V3,
    GET_INTEGRATION_CONFIG_V3,
    SET_INTEGRATION_CONFIG_V3,
    DELETE_INTEGRATION_V3,
    EXPORT_INTEGRATION_V3,
    IMPORT_INTEGRATION_V3,
    UPDATE_INTEGRATION_V3,
    PUBLISH_TOOLS_V3
)
from pygeai.lab.clients import AILabClient


class IntegrationClient(AILabClient):
    """
    Client for Integration API operations (v3 endpoints).

    This client provides methods to interact with integration endpoints for managing
    integrations (tool-plugins), including shared parameter configuration, inheritance,
    and batch operations.
    """

    def list_integrations(
            self,
            name: Optional[str] = None,
            id: Optional[str] = None,
            access_scope: Optional[str] = None,
            count: int = 100,
            start: int = 0,
            detail_level: str = "normal",
            allow_drafts: bool = True,
            allow_external: bool = True
    ) -> dict:
        """
        Retrieves a list of integrations associated with the specified project.

        :param name: Optional[str] - Filter by integration name. Supports partial matching
            when prefixed with '%' (URL-encoded as '%25'). Defaults to None.
        :param id: Optional[str] - Filter by integration ID. Defaults to None.
        :param access_scope: Optional[str] - Filter by access scope ('private', 'public', 'project').
            Defaults to None.
        :param count: int - Number of integrations to retrieve. Defaults to 100.
        :param start: int - Pagination offset. Defaults to 0.
        :param detail_level: str - Response detail level ('normal' or 'expanded'). Defaults to 'normal'.
        :param allow_drafts: bool - Whether to include draft integrations. Defaults to True.
        :param allow_external: bool - Whether to include external integrations. Defaults to True.
        :return: dict - JSON response containing the list of integrations and pagination information.
            Expected format: { 'toolPlugins': [...], 'information': { 'Duration', 'Page', 'PageSize', 'Total' } }
        :raises InvalidAPIResponseException: If the API returns an error status code.

        Example:
            >>> client = IntegrationClient(api_key="key", base_url="url", project_id="proj")
            >>> integrations = client.list_integrations(count=10, access_scope="private")
        """
        endpoint = LIST_INTEGRATIONS_V3

        params = {
            "count": count,
            "start": start,
            "detailLevel": detail_level,
            "allowDrafts": allow_drafts,
            "allowExternal": allow_external
        }

        if name is not None:
            params["name"] = name
        if id is not None:
            params["id"] = id
        if access_scope is not None:
            params["accessScope"] = access_scope

        logger.debug(f"Listing integrations for project {self.project_id} with params: {params}")

        response = self.api_service.get(
            endpoint=endpoint,
            params=params
        )

        validate_status_code(response)
        return parse_json_response(response, f"list integrations for project {self.project_id}")

    def get_integration(
            self,
            integration_id_or_name: str,
            detail_level: str = "normal",
            allow_drafts: bool = True
    ) -> dict:
        """
        Retrieves details of a specific integration from the specified project.

        :param integration_id_or_name: str - Unique identifier or name of the integration to retrieve.
        :param detail_level: str - Response detail level ('normal' or 'expanded'). 'expanded' includes
            detailed information about tools within the integration. Defaults to 'normal'.
        :param allow_drafts: bool - Whether to include draft integration in the retrieval. Defaults to True.
        :return: dict - JSON response containing the integration details.
            Expected format: { 'plugin': { 'id', 'name', ... } }
        :raises InvalidAPIResponseException: If the API returns an error status code.

        Example:
            >>> client = IntegrationClient(api_key="key", base_url="url", project_id="proj")
            >>> integration = client.get_integration("my-integration", detail_level="expanded")
        """
        endpoint = GET_INTEGRATION_V3.format(integrationIdOrName=integration_id_or_name)

        params = {
            "detailLevel": detail_level,
            "allowDrafts": allow_drafts
        }

        logger.debug(f"Retrieving integration {integration_id_or_name} for project {self.project_id}")

        response = self.api_service.get(
            endpoint=endpoint,
            params=params
        )

        validate_status_code(response)
        return parse_json_response(
            response,
            f"get integration {integration_id_or_name} for project {self.project_id}"
        )

    def get_config(
            self,
            integration_id_or_name: str,
            allow_drafts: bool = True
    ) -> dict:
        """
        Retrieves the configuration (shared parameters) for a specific integration.

        This method returns the parameters defined at the integration level, which can be inherited
        by tools belonging to the integration.

        :param integration_id_or_name: str - Unique identifier or name of the integration.
        :param allow_drafts: bool - Whether to include draft configuration. Defaults to True.
        :return: dict - JSON response containing the integration configuration.
            Expected format: { 'parameters': [ { 'key', 'dataType', 'isRequired', 'type', 'value', ... }, ... ] }
        :raises InvalidAPIResponseException: If the API returns an error status code.

        Example:
            >>> client = IntegrationClient(api_key="key", base_url="url", project_id="proj")
            >>> config = client.get_config("my-integration")
            >>> parameters = config.get("parameters", [])
        """
        endpoint = GET_INTEGRATION_CONFIG_V3.format(integrationIdOrName=integration_id_or_name)

        params = {
            "allowDrafts": allow_drafts
        }

        logger.debug(
            f"Retrieving config for integration {integration_id_or_name} in project {self.project_id}"
        )

        response = self.api_service.get(
            endpoint=endpoint,
            params=params
        )

        validate_status_code(response)
        return parse_json_response(
            response,
            f"get config for integration {integration_id_or_name} in project {self.project_id}"
        )

    def set_config(
            self,
            integration_id_or_name: str,
            parameters: list
    ) -> dict:
        """
        Sets or updates the configuration (shared parameters) for a specific integration.

        This method allows setting parameter values at the integration level, which can then be inherited
        by tools belonging to the integration.

        :param integration_id_or_name: str - Unique identifier or name of the integration.
        :param parameters: list - List of parameter dictionaries defining the integration's shared parameters.
            Each dictionary must contain 'key', 'type', 'value', and optionally other fields like 'description',
            'dataType', 'isRequired', 'fromSecret'.
        :return: dict - JSON response containing the result of the set operation if successful.
            Returns empty dict for 204 No Content responses.
        :raises ValueError: If parameters list is None or empty.
        :raises InvalidAPIResponseException: If the API returns an error status code.

        Example parameters:
            [
                {
                    "key": "securityScheme.flows.authorizationCode.clientId",
                    "type": "config",
                    "value": "myGoogleClientIdSecret",
                    "description": "The name of the secret that contains the value of clientId",
                    "dataType": "String",
                    "isRequired": True,
                    "fromSecret": True
                }
            ]

        Example:
            >>> client = IntegrationClient(api_key="key", base_url="url", project_id="proj")
            >>> params = [{"key": "api_key", "type": "config", "value": "secret-name", "fromSecret": True}]
            >>> result = client.set_config("my-integration", params)
        """
        if not parameters:
            raise ValueError("Parameters list must be provided and non-empty.")

        endpoint = SET_INTEGRATION_CONFIG_V3.format(integrationIdOrName=integration_id_or_name)

        data = {
            "parameterDefinition": {
                "parameters": parameters
            }
        }

        logger.debug(
            f"Setting config for integration {integration_id_or_name} in project {self.project_id} "
            f"with {len(parameters)} parameter(s)"
        )

        response = self.api_service.post(
            endpoint=endpoint,
            data=data
        )

        # Accept both 200 and 204 status codes
        if response.status_code not in [200, 204]:
            logger.error(
                f"Unable to set config for integration {integration_id_or_name} in project {self.project_id}: "
                f"status {response.status_code}. Response: {response.text}"
            )
            raise InvalidAPIResponseException(
                f"Unable to set config for integration {integration_id_or_name} in project {self.project_id}: "
                f"{response.text}"
            )

        # Return empty dict for 204, parse JSON for 200
        if response.status_code == 204:
            return {}
        else:
            return parse_json_response(
                response,
                f"set config for integration {integration_id_or_name} in project {self.project_id}"
            )

    def delete_integration(
            self,
            integration_id_or_name: str
    ) -> dict:
        """
        Deletes a specific integration from the specified project.

        This method sends a request to delete the integration identified by either ID or name.
        Deleting an integration may also delete all tools within it, depending on server configuration.

        :param integration_id_or_name: str - Unique identifier or name of the integration to delete.
        :return: dict - JSON response if any, or empty dict for 204 No Content.
        :raises InvalidAPIResponseException: If the API returns an error status code.

        Example:
            >>> client = IntegrationClient(api_key="key", base_url="url", project_id="proj")
            >>> result = client.delete_integration("my-integration")
        """
        endpoint = DELETE_INTEGRATION_V3.format(integrationIdOrName=integration_id_or_name)

        logger.debug(
            f"Deleting integration {integration_id_or_name} from project {self.project_id}"
        )

        response = self.api_service.delete(endpoint=endpoint)

        # Accept both 200 and 204 status codes
        if response.status_code not in [200, 204]:
            logger.error(
                f"Unable to delete integration {integration_id_or_name} from project {self.project_id}: "
                f"status {response.status_code}. Response: {response.text}"
            )
            raise InvalidAPIResponseException(
                f"Unable to delete integration {integration_id_or_name} from project {self.project_id}: "
                f"HTTP {response.status_code} - {response.text[:200]}"
            )

        # Return empty dict for 204, parse JSON for 200
        if response.status_code == 204:
            return {}
        else:
            return parse_json_response(
                response,
                f"delete integration {integration_id_or_name} from project {self.project_id}"
            )

    def export_integration(
            self,
            integration_id_or_name: str
    ) -> dict:
        """
        Exports an integration with all its tools and configuration.

        This method retrieves the complete integration definition including all tools,
        shared parameters, and metadata, suitable for importing into another project.

        :param integration_id_or_name: str - Unique identifier or name of the integration to export.
        :return: dict - JSON response containing the complete integration export data.
        :raises InvalidAPIResponseException: If the API returns an error status code or endpoint is not available.

        Note: This endpoint may not be available yet in all API versions. If it returns 404,
        the export functionality is not implemented on the server.

        Example:
            >>> client = IntegrationClient(api_key="key", base_url="url", project_id="proj")
            >>> export_data = client.export_integration("my-integration")
        """
        endpoint = EXPORT_INTEGRATION_V3.format(integrationIdOrName=integration_id_or_name)

        logger.debug(
            f"Exporting integration {integration_id_or_name} from project {self.project_id}"
        )

        try:
            response = self.api_service.get(endpoint=endpoint)
            validate_status_code(response)
            return parse_json_response(
                response,
                f"export integration {integration_id_or_name} from project {self.project_id}"
            )
        except Exception as e:
            if "404" in str(e):
                raise InvalidAPIResponseException(
                    f"Export integration endpoint is not available yet. "
                    f"This feature may not be implemented on the server. "
                    f"Endpoint: {endpoint}"
                ) from e
            raise

    def create_integration(
            self,
            name: str,
            description: str,
            manifest: str,
            access_scope: str = "private",
            automatic_publish: bool = False
    ) -> dict:
        """
        Creates a new integration from an OpenAPI manifest.

        This method creates a new integration in the project using an OpenAPI specification
        manifest that defines the tools and their parameters.

        :param name: str - Name of the integration.
        :param description: str - Description of what the integration does.
        :param manifest: str - OpenAPI 3.0 specification as a JSON string defining the API.
        :param access_scope: str - Access scope: "private" (default) or "public".
        :param automatic_publish: bool - Whether to automatically publish the tools after creation.
        :return: dict - JSON response containing the created integration details.
        :raises ValueError: If required parameters are missing.
        :raises InvalidAPIResponseException: If the API returns an error status code.

        Example:
            >>> client = IntegrationClient(api_key="key", base_url="url", project_id="proj")
            >>> manifest = '{"openapi": "3.0.0", "info": {...}, "servers": [...], "paths": {...}}'
            >>> result = client.create_integration(
            ...     name="My Integration",
            ...     description="Integration description",
            ...     manifest=manifest
            ... )
        """
        if not all([name, description, manifest]):
            raise ValueError("Name, description, and manifest are required.")

        endpoint = IMPORT_INTEGRATION_V3
        if automatic_publish:
            endpoint += "?automaticPublish=true"

        payload = {
            "toolPlugin": {
                "name": name,
                "description": description,
                "accessScope": access_scope,
                "manifest": manifest
            }
        }

        logger.debug(
            f"Creating integration '{name}' in project {self.project_id}"
        )

        response = self.api_service.post(
            endpoint=endpoint,
            data=payload
        )

        validate_status_code(response)
        return parse_json_response(
            response,
            f"create integration '{name}' in project {self.project_id}"
        )

    def import_integration(
            self,
            integration_data: dict
    ) -> dict:
        """
        Imports an integration from exported data or raw integration data.

        This method creates a new integration in the project. It accepts either:
        - Export data format (if export becomes available)
        - Raw integration data with toolPlugin wrapper

        :param integration_data: dict - The integration data to import.
            Should contain either exported format or {"toolPlugin": {...}} format.
        :return: dict - JSON response containing the imported integration details.
        :raises ValueError: If integration_data is None or empty.
        :raises InvalidAPIResponseException: If the API returns an error status code.

        Example:
            >>> client = IntegrationClient(api_key="key", base_url="url", project_id="proj")
            >>> data = {"toolPlugin": {"name": "...", "manifest": "..."}}
            >>> result = client.import_integration(data)
        """
        if not integration_data:
            raise ValueError("Integration data must be provided and non-empty.")

        endpoint = IMPORT_INTEGRATION_V3

        logger.debug(
            f"Importing integration into project {self.project_id}"
        )

        response = self.api_service.post(
            endpoint=endpoint,
            data=integration_data
        )

        validate_status_code(response)
        return parse_json_response(
            response,
            f"import integration into project {self.project_id}"
        )

    def update_integration(
            self,
            integration_id_or_name: str,
            name: str,
            description: str,
            manifest: str,
            access_scope: str = "private",
            automatic_publish: bool = False
    ) -> dict:
        """
        Updates an existing integration by re-importing it with a new manifest.

        This method replaces the integration's definition with the provided manifest,
        updating all tools defined in it. Existing tools not in the new manifest
        may be removed or deprecated depending on server configuration.

        :param integration_id_or_name: str - Unique identifier or name of the integration to update.
        :param name: str - New name for the integration.
        :param description: str - New description for the integration.
        :param manifest: str - Updated OpenAPI 3.0 specification as a JSON string.
        :param access_scope: str - Access scope: "private" (default) or "public".
        :param automatic_publish: bool - Whether to automatically publish the tools after update.
        :return: dict - JSON response containing the updated integration details.
        :raises ValueError: If required parameters are missing.
        :raises InvalidAPIResponseException: If the API returns an error status code.

        Example:
            >>> client = IntegrationClient(api_key="key", base_url="url", project_id="proj")
            >>> updated_manifest = '{\"openapi\": \"3.0.0\", ...}'
            >>> result = client.update_integration(
            ...     integration_id_or_name="my-integration",
            ...     name="My Integration",
            ...     description="Updated description",
            ...     manifest=updated_manifest
            ... )
        """
        if not all([integration_id_or_name, name, description, manifest]):
            raise ValueError("integration_id_or_name, name, description, and manifest are required.")

        endpoint = UPDATE_INTEGRATION_V3.format(integrationIdOrName=integration_id_or_name)
        if automatic_publish:
            endpoint += "?automaticPublish=true"

        payload = {
            "toolPlugin": {
                "name": name,
                "description": description,
                "accessScope": access_scope,
                "manifest": manifest
            }
        }

        logger.debug(
            f"Updating integration '{integration_id_or_name}' in project {self.project_id}"
        )

        response = self.api_service.put(
            endpoint=endpoint,
            data=payload
        )

        validate_status_code(response)
        return parse_json_response(
            response,
            f"update integration '{integration_id_or_name}' in project {self.project_id}"
        )

    def publish_tools(
            self,
            integration_id_or_name: str
    ) -> dict:
        """
        Publishes all tools in an integration, making them available for use.

        After creating or updating an integration, tools are in draft/pending state.
        This method publishes them so they can be used by agents.

        :param integration_id_or_name: str - Unique identifier or name of the integration.
        :return: dict - JSON response containing the result of the publish operation.
        :raises InvalidAPIResponseException: If the API returns an error status code.

        Example:
            >>> client = IntegrationClient(api_key="key", base_url="url", project_id="proj")
            >>> result = client.publish_tools("my-integration")
        """
        endpoint = PUBLISH_TOOLS_V3.format(integrationIdOrName=integration_id_or_name)

        logger.debug(
            f"Publishing tools for integration {integration_id_or_name} in project {self.project_id}"
        )

        response = self.api_service.post(endpoint=endpoint, data={})

        # Accept both 200 and 204
        if response.status_code not in [200, 204]:
            raise InvalidAPIResponseException(
                f"Unable to publish tools for integration {integration_id_or_name}: "
                f"HTTP {response.status_code} - {response.text[:200]}"
            )

        if response.status_code == 204:
            return {}
        return parse_json_response(
            response,
            f"publish tools for integration {integration_id_or_name} in project {self.project_id}"
        )
