from typing import List, Optional

from allensdk.core import (
    DataObject,
    JsonReadableInterface,
    LimsReadableInterface,
    NwbReadableInterface,
)
from allensdk.internal.api import PostgresQueryMixin
from pynwb import NWBFile


class TargetedImagingDepth(
    DataObject,
    LimsReadableInterface,
    NwbReadableInterface,
    JsonReadableInterface,
):
    """Data object loads and stores the average `imaging_depth`s
    (microns) across experiments in the container that an experiment is
    associated with.
    """

    def __init__(self, targeted_imaging_depth: int):
        super().__init__(
            name="targeted_imaging_depth", value=targeted_imaging_depth
        )

    @classmethod
    def from_lims(
        cls,
        ophys_experiment_id: int,
        lims_db: PostgresQueryMixin,
        ophys_experiment_ids: Optional[List[int]] = None,
    ) -> "TargetedImagingDepth":
        """Load targeted imaging depth.

        Parameters
        ----------
        ophys_experiment_id : int
            Id of experiment to calculate targeted depth for.
        lims_db : PostgresQueryMixin
            Connection to the LIMS2 database.
        ophys_experiment_ids : list of int
            Subset of experiments in the container of ``ophys_experiment_id``
            to calculate the target_imaging_depth. List should contain
            the value of ``ophys_experiment_id``.
        """
        query_container_id = """
            SELECT visual_behavior_experiment_container_id
            FROM ophys_experiments_visual_behavior_experiment_containers
            WHERE ophys_experiment_id = {}
        """.format(
            ophys_experiment_id
        )

        container_id = lims_db.fetchone(query_container_id, strict=True)

        query_depths = """
            SELECT oe.id as ophys_experiment_id, imd.depth as depth
            FROM ophys_experiments_visual_behavior_experiment_containers ec
            JOIN ophys_experiments oe ON oe.id = ec.ophys_experiment_id
            LEFT JOIN imaging_depths imd ON imd.id = oe.imaging_depth_id
            WHERE ec.visual_behavior_experiment_container_id = {};
        """.format(
            container_id
        )
        depths = lims_db.select(query_depths).set_index("ophys_experiment_id")
        if ophys_experiment_ids is not None:
            if ophys_experiment_id not in ophys_experiment_ids:
                raise ValueError(
                    "List of ophys_exeperiment_ids does not contain id of "
                    "this experiment. Exiting. \n"
                    f"\tophys_experiment_id={ophys_experiment_id}\n"
                    f"\tophys_experiment_id list={ophys_experiment_ids}\n"
                )
            depths = depths.loc[ophys_experiment_ids]

        targeted_imaging_depth = int(depths["depth"].mean())
        return cls(targeted_imaging_depth=targeted_imaging_depth)

    @classmethod
    def from_json(cls, dict_repr: dict) -> "TargetedImagingDepth":
        # TODO remove all of the from_json loading and validation step
        # ticket 2607
        return cls(targeted_imaging_depth=dict_repr["targeted_depth"])

    @classmethod
    def from_nwb(cls, nwbfile: NWBFile) -> "TargetedImagingDepth":
        try:
            metadata = nwbfile.lab_meta_data["metadata"]
            return cls(targeted_imaging_depth=metadata.targeted_imaging_depth)
        except AttributeError:
            return None
