from collections import OrderedDict
from biolib.typing_utils import List, Optional

from biolib import api
from biolib.experiments.types import ExperimentDict
from biolib.jobs.job import Job

from biolib.tables import BioLibTable


class Experiment:
    _BIOLIB_EXPERIMENTS: List['Experiment'] = []

    # Columns to print in table when showing Job
    _table_columns_to_row_map = OrderedDict({
        'Name': {'key': 'name', 'params': {}},
        'Amount of jobs': {'key': 'jobs_count', 'params': {}},
        'Created At': {'key': 'created_at', 'params': {}}
    })

    def __init__(self, name: str):
        self._jobs: List[Job] = []
        self._experiment_dict: ExperimentDict = self._create_in_backend_or_get_experiment_dict(name)

    def __enter__(self):
        Experiment._BIOLIB_EXPERIMENTS.append(self)

    def __exit__(self, type, value, traceback):  # pylint: disable=redefined-builtin
        Experiment._BIOLIB_EXPERIMENTS.pop()

    def __str__(self):
        return f'Experiment: {self.name}'

    def __repr__(self):
        return f'Experiment: {self.name}'

    @property
    def uuid(self) -> str:
        return self._experiment_dict['uuid']

    @property
    def name(self) -> str:
        return self._experiment_dict['name']

    @staticmethod
    def get_experiment_in_context() -> Optional['Experiment']:
        if Experiment._BIOLIB_EXPERIMENTS:
            return Experiment._BIOLIB_EXPERIMENTS[-1]
        return None

    # Prints a table listing info about experiments accessible to the user
    @staticmethod
    def show_experiments(count: int = 25) -> None:
        experiment_dicts = api.client.get(
            url='/experiments/',
            params={
                'page_size': str(count)
            }
        ).json()['results']
        BioLibTable(
            columns_to_row_map=Experiment._table_columns_to_row_map,
            rows=experiment_dicts,
            title='Experiments'
        ).print_table()

    # Prints a table containing info about this experiment
    def show(self) -> None:
        self._refetch_jobs()
        BioLibTable(
            columns_to_row_map=Experiment._table_columns_to_row_map,
            rows=[{**self._experiment_dict, 'jobs_count': len(self._jobs)}],
            title=f'Experiment: {self.name}'
        ).print_table()

    # Prints a table listing info about the jobs in this experiment
    def show_jobs(self) -> None:
        self._refetch_jobs()
        BioLibTable(
            columns_to_row_map=Job.table_columns_to_row_map,
            rows=[job._job_dict for job in self._jobs],  # pylint: disable=protected-access
            title=f'Jobs in experiment: "{self.name}"'
        ).print_table()

    def get_jobs(self, status: Optional[str] = None) -> List[Job]:
        # TODO: Consider taking a "filters" dictionary with possible filters
        self._refetch_jobs()
        if status:
            return [job for job in self._jobs if job._job_dict['state'] == status]  # pylint: disable=protected-access

        return self._jobs

    def _refetch_jobs(self) -> None:
        job_dicts = api.client.get(url=f'/experiments/{self.uuid}/jobs/').json()['results']
        self._jobs = [Job(job_dict=job_dict) for job_dict in job_dicts]

    def _create_in_backend_or_get_experiment_dict(self, name: str) -> ExperimentDict:
        # This endpoint returns experiment dict if already created
        experiment_dict: ExperimentDict = api.client.post(
            path='/experiments/',
            data={
                'name': name
            }
        ).json()
        return experiment_dict
