from typing import Dict, Any
from .models import MethodConfig, MethodRepoMethod
from .client import TerraClient
from bioforklift.forklift_logging import setup_logger

logger = setup_logger(__name__)


class TerraMethods:
    """
    Class meant to handle Terra workflows and method configurations
    See https://github.com/broadinstitute/fiss/blob/b3fa2a0d888610e04f744f9e661fa32e46a5af95/firecloud/api.py#L650
    """


    def __init__(self, client: TerraClient):
        self.client = client

    def generate_method_config(
        self,
        repo_uri: str,
        wf_name: str,
        table_name: str,
        inputs_dict: dict,
        outputs_dict: dict,
        branch: str,
        source_repo: str = "dockstore",
    ) -> MethodConfig:
        """
        Generate a MethodConfig object for a workflow
        
        Args:
            repo_uri: URI of the repository containing the workflow
            wf_name: Name of the workflow
            table_name: Name of the Terra entity table
            inputs_dict: Dictionary of workflow inputs
            outputs_dict: Dictionary of workflow outputs
            branch: Branch of the repository
            source_repo: Source repository type (default: "dockstore")

        Returns:
            MethodConfig object representing the workflow configuration    
        """
        # to be removed when added natively
        method_config = MethodConfig(
            namespace=self.client.destination_project,
            name=wf_name,
            rootEntityType=f"{table_name}_set",
            inputs=inputs_dict,
            outputs=outputs_dict,
            prerequisites={},
            methodRepoMethod=MethodRepoMethod(
                sourceRepo=source_repo,
                methodPath=f"{repo_uri}/{wf_name}",
                methodVersion=branch,
            ),
            methodConfigVersion=0,
            deleted=False,
        )
        return method_config
    

    def get_method_config(
        self,
        config_name: str,
        use_destination: bool = False,
    ) -> Dict[str, Any]:
        """
        Get a workspace method configuration by name. By default pulls from source workspace.

        Args:
            config_name: Name of the method configuration to retrieve

        Returns:
            Dict containing the method configuration details
        """
        client_project = self.client.destination_project if use_destination else self.client.source_project

        logger.info(f"Fetching workspace method configuration: {config_name} from project: {client_project}")
        response = self.client.get(
          f"method_configs/{client_project}/{config_name}",
          use_destination=use_destination,
        )
        return response.json()


    def overwrite_method_config(
        self,
        config: MethodConfig,
        use_destination: bool = True,
    ) -> Dict[str, Any]:
        """
        Add or overwrite a new workspace method configuration. AKA create a new workflow in Terra with inputs/outputs defined.
        By default pulls from destination workspace.

        Args:
            config: MethodConfig containing all configuration details

        Returns:
            Dict containing the created method configuration details
        """
        client_project = self.client.destination_project if use_destination else self.client.source_project

        logger.info(f"Uploading workspace method configuration: {config.name} to project: {client_project}")
        return self.client.put(
            f"method_configs/{client_project}/{config.name}",
            data=config.model_dump(exclude_none=True),
            use_destination=use_destination,
        ).json()


    def method_config_validate(
      self,
      config: MethodConfig,
      use_destination: bool = True,
    ) -> Dict[str, Any]:
        """
        Validate a workspace method configuration. Note that this can only validate existing configurations in the workspace.
        Meaning you must first call `overwrite_method_config` before validating a new configuration.
        Args:
            config: MethodConfig containing all configuration details

        Returns:
            Dict containing the validation results
        """
        client_project = self.client.destination_project if use_destination else self.client.source_project

        logger.info(f"Validating workspace method configuration: {config.name} from project: {client_project}")
        response = self.client.get(
            f"method_configs/{client_project}/{config.name}/validate",
            use_destination=use_destination,
        ).json()

        # Check for invalid inputs/outputs and raise error if found
        validation_error_keys = ["invalidInputs", "invalidOutputs", "missingInputs", "extraInputs"]
        invalid_fields = {
            key: response.get(key)
            for key in validation_error_keys
            if response.get(key)
        }

        if invalid_fields:
            msg = "MethodConfig validation errors found:\n" + "\n".join(
                f"- {key}: {val}" for key, val in invalid_fields.items()
            )
            logger.error(msg)
            raise ValueError(msg)

        return response


    def dict_to_method_config(
        self,
        config_dict: Dict[str, Any]
    ) -> MethodConfig:
        """
        Convert a dictionary to a MethodConfig object without making an API call.
        Useful for modifying existing configurations.

        Args:
            config_dict: Dict containing method configuration details

        Returns:
            MethodConfig object constructed from the dictionary
        """
        return MethodConfig.model_validate(config_dict)