"""
This private module in qcforward is used to check wellzonation vs grid zonation
"""

import collections
import json
from pathlib import Path

import fmu.tools
import numpy as np
from fmu.tools._common import _QCCommon
from fmu.tools.qcforward._qcforward import ActionsParser, QCForward
from jsonschema import validate

QCC = _QCCommon()
UNDEF = float("nan")


class _LocalData:
    def __init__(self):
        """Defining and hold data local for this routine"""

        self.zonelogname = "Zonelog"
        self.zonelogrange = [0, 99]
        self.zonelogshift = 0
        self.depthrange = [0.0, 999999.0]
        self.actions = None
        self.perflogname = None
        self.perflogrange = [1, 9999]
        self.gridzone = None
        self.gridzonerange = [1, 9999]
        self.wellresample = None
        self.infotext = "ZONELOG MATCH"
        self.nametag = None
        self.reportfile = None

    def parse_data(self, data):
        """Parsing the actual data"""

        # TODO: verify and qc
        self.nametag = data.get("nametag", "unset_nametag")
        if "report" in data:
            self.reportfile = (
                data["report"].get("file")
                if isinstance(data["report"], dict)
                else data["report"]
            )

        self.zonelogname = data["zonelog"]["name"]
        self.zonelogrange = data["zonelog"]["range"]
        self.zonelogshift = data["zonelog"].get("shift", 0)

        self.depthrange = data.get("depthrange", [0.0, 99999.0])

        self.actions = data["actions"]

        if "perflog" in data.keys() and data["perflog"]:
            self.perflogname = data["perflog"].get("name", None)
            self.perflogrange = data["perflog"].get("range", [1, 9999])
            self.infotext = "PERFLOG MATCH"

        if "well_resample" in data.keys():
            self.wellresample = data.get("well_resample", None)


class WellZonationVsGrid(QCForward):
    def run(self, data, reuse=False, project=None):
        """Main routine for evaulating well zonation match in 3D grids.

        The routine depends on existing XTGeo functions for this purpose

        Args:
            data (dict or str): The input data either as a Python dictionary or
                a path to a YAML file
            reuse (bool or list): Reusing some "timeconsuming to read" data in the
                instance. If True, then grid and gridprops will be reused as default.
                Alternatively it can be a list for more fine grained control, e.g.
                ["grid", "gridprops", "wells"]
            project (Union[object, str]): For usage inside RMS

        """
        data = self._data = self.handle_data(data, project)
        self._validate_input(self._data, project)

        QCC.verbosity = data.get("verbosity", 0)

        # parse data that are special (local) for this check
        QCC.print_info("Parsing additional data...")
        self.ldata = _LocalData()
        self.ldata.parse_data(data)

        # parsing data stored is self._xxx (general data like grid)
        QCC.print_info("Parsing local data...")
        lognames = [self.ldata.zonelogname]
        if self.ldata.perflogname:
            lognames.append(self.ldata.perflogname)

        wsettings = {
            "lognames": lognames,
            "depthrange": self.ldata.depthrange,
            "rescale": self.ldata.wellresample,
        }

        QCC.print_info("Parsing grid, gridprops, .... data...")
        self.gdata.parse(
            project=project, data=data, reuse=reuse, wells_settings=wsettings
        )

        self.ldata.gridzone = self.gdata.gridprops.props[0]

        actions = self._data.get("actions", None)
        if actions is None or not isinstance(actions, list):
            raise ValueError("No actions are defined or wrong data-input for actions")

        # need to evaluate once per well anyway, also of only "all"; this will fill
        # the MATCH% and WELL column in the dataframe given as an OrderedDict
        QCC.print_info("Each well find zonelog match")
        wellmatches = self._evaluate_wells()

        QCC.print_debug(list(wellmatches.keys()))
        QCC.print_debug(list(wellmatches.values()))

        # results are stored in a dict based table which be turned into a Pandas
        # dataframe in the end (most efficient; then turn into pandas at end)
        result = collections.OrderedDict(
            [
                ("WELL", []),
                ("WARNRULE", []),
                ("STOPRULE", []),
                ("MATCH%", []),
                ("STATUS", []),
            ]
        )

        for therule in actions:

            warnrule = ActionsParser(
                therule.get("warn", None), mode="warn", verbosity=QCC.verbosity
            )
            stoprule = ActionsParser(
                therule.get("stop", None), mode="stop", verbosity=QCC.verbosity
            )

            QCC.print_debug(f"WARN RULE {warnrule.status}  {warnrule.expression}")
            QCC.print_debug(f"STOP RULE {stoprule.status}  {stoprule.expression}")

            for well, actualmatch in wellmatches.items():

                status = None
                QCC.print_debug(f"Loop well {well} which has match {actualmatch}")

                for num, issue in enumerate([warnrule, stoprule]):
                    # both issues will be on same line
                    QCC.print_debug(f"Issue no {num} {issue.mode} {issue.expression}")

                    if issue.all and well != "all" or not issue.all and well == "all":
                        continue

                    if status is None:
                        status = "OK"

                    if num == 0:
                        result["WELL"].append(well)
                        result["MATCH%"].append(actualmatch)

                    if issue.status is None:
                        result[issue.mode.upper() + "RULE"].append(UNDEF)
                        status = "OK"
                        continue

                    result[issue.mode.upper() + "RULE"].append(issue.expression)
                    if issue.compare == ">" and actualmatch > issue.limit:
                        status = issue.mode.upper()
                    elif issue.compare == "<" and actualmatch < issue.limit:
                        status = issue.mode.upper()

                if status is not None:
                    result["STATUS"].append(status)

        dfr = self.make_report(
            result, reportfile=self.ldata.reportfile, nametag=self.ldata.nametag
        )
        dfr.set_index("WELL", inplace=True)
        self.evaluate_qcreport(dfr, "well zonation vs grid")

    @staticmethod
    def _validate_input(data, project):
        """Validate data against JSON schemas. TODO complete JSON files"""

        spath = Path(fmu.tools.__file__).parent / "qcforward" / "_schemas"

        schemafile = "wellzonation_vs_grid_asfile.json"

        if project:
            schemafile = "wellzonation_vs_grid_asroxapi.json"

        with open((spath / schemafile), "r") as thisschema:
            schema = json.load(thisschema)

        validate(instance=data, schema=schema)

    def _evaluate_wells(self):
        """Do a check per well and the sum; return an Ordered Dict"""

        wells = []
        matches = []

        for wll in self.gdata.wells.wells:
            QCC.print_debug("Working with well {}".format(wll.name))

            dfr = wll.dataframe

            if self.ldata.zonelogname not in dfr.columns:
                print(
                    "Well {} have no requested zonelog <{}> and will be skipped".format(
                        wll.name,
                        self.ldata.zonelogname,
                    )
                )
                continue

            if self.ldata.perflogname and self.ldata.perflogname not in dfr.columns:
                print(
                    "Well {} have no requested perflog <{}> and will be skipped".format(
                        wll.name,
                        self.ldata.perflogname,
                    )
                )
                continue

            QCC.print_debug("XTGeo work for {}...".format(wll.name))
            res = self.gdata.grid.report_zone_mismatch(
                well=wll,
                zonelogname=self.ldata.zonelogname,
                zoneprop=self.ldata.gridzone,
                zonelogrange=self.ldata.zonelogrange,
                zonelogshift=self.ldata.zonelogshift,
                depthrange=self.ldata.depthrange,
                perflogname=self.ldata.perflogname,
                perflogrange=self.ldata.perflogrange,
                resultformat=2,
            )
            QCC.print_debug("XTGeo work for {}... done".format(wll.name))

            wells.append(wll.name)

            if res:
                matches.append(res["MATCH2"])

            else:
                matches.append(UNDEF)

        # finally averages, for "all" results
        match_allv = np.array(matches)
        mmean = np.nanmean(match_allv)
        wells.append("all")
        matches.append(mmean)

        return collections.OrderedDict(zip(wells, matches))
