"""
Create a run summary for a given date containing the number of subruns,
the start time of the run, type pf the run: DATA, DRS4, CALI, and
the reference timestamp and counter of the run.
"""

import argparse
import logging
from collections import Counter
from datetime import datetime
from glob import glob
from pathlib import Path

import numpy as np
from astropy.table import Table
from astropy.time import Time
from ctapipe.containers import EventType
from ctapipe_io_lst import (CDTS_AFTER_37201_DTYPE, CDTS_BEFORE_37201_DTYPE,
                            DRAGON_COUNTERS_DTYPE, LSTEventSource, MultiFiles)
from ctapipe_io_lst.event_time import combine_counters
from traitlets.config import Config

from lstchain import __version__
from lstchain.paths import parse_r0_filename

log = logging.getLogger(__name__)

parser = argparse.ArgumentParser(description="Create run summary file")

parser.add_argument(
    "-d",
    "--date",
    help="Date for the creation of the run summary in format YYYYMMDD",
    required=True,
)

parser.add_argument(
    "--r0-path",
    type=Path,
    help="Path to the R0 files. Default is /fefs/aswg/data/real/R0",
    default=Path("/fefs/aswg/data/real/R0"),
)

parser.add_argument(
    "-o",
    "--output-dir",
    type=Path,
    help="Directory in which Run Summary file is written",
    default="/fefs/aswg/data/real/monitoring/RunSummary",
)

dtypes = {
    "ucts_timestamp": np.int64,
    "run_start": np.int64,
    "dragon_reference_time": np.int64,
    "dragon_reference_module_id": np.int16,
    "dragon_reference_module_index": np.int16,
    "dragon_reference_counter": np.uint64,
    "dragon_reference_source": str,
}


def get_list_of_files(r0_path):
    """
    Get the list of R0 files from a given date.

    Parameters
    ----------
    r0_path : pathlib.Path
        Path to the R0 files

    Returns
    -------
    list_of_files: pathlib.Path.glob
        List of files
    """
    return r0_path.glob("LST*.fits.fz")


def get_list_of_runs(list_of_files):
    """
    Get the sorted list of run objects from R0 filenames.

    Parameters
    ----------
    list_of_files : pathlib.Path.glob
        List of files
    Returns
    -------
    list_of_run_objects
    """
    return sorted(parse_r0_filename(file) for file in list_of_files)


def get_runs_and_subruns(list_of_run_objects, stream=1):
    """
    Get the list of run numbers and the number of sequential files (subruns)
    of each run.

    Parameters
    ----------
    list_of_run_objects
    stream: int, optional
        Number of the stream to obtain the number of sequential files (default is 1).

    Returns
    -------
    (run, number_of_files) : tuple
        Run numbers and corresponding subrun of each run.
    """
    list_filtered_stream = filter(lambda x: x.stream == stream, list_of_run_objects)

    run, number_of_files = np.unique(
        list(map(lambda x: x.run, list_filtered_stream)), return_counts=True
    )

    return run, number_of_files


def type_of_run(date_path, run_number, counters, n_events=500):
    """
    Guessing empirically the type of run based on the percentage of
    pedestals/mono trigger types from the first n_events:
    DRS4 pedestal run (DRS4): 100% mono events (trigger_type == 1)
    cosmic data run (DATA): <10% pedestal events (trigger_type == 32)
    pedestal-calibration run (PEDCALIB): ~50% mono, ~50% pedestal events
    Otherwise (ERROR) the run is not expected to be processed.
    This method may not give always the correct type.
    At some point this should be taken directly from TCU.

    Parameters
    ----------
    date_path : pathlib.Path
        Path to the R0 files
    run_number : int
        Run id
    counters : dict
        Dict containing the reference counters and timestamps
    n_events : int
        Number of events used to infer the type of the run

    Returns
    -------
    run_type: str
        Type of run (DRS4, PEDCALIB, DATA, ERROR)
    """

    pattern = f"LST-1.1.Run{run_number:05d}.0000*.fits.fz"
    list_of_files = sorted(date_path.glob(pattern))
    if len(list_of_files) == 0:
        log.error(f"First subrun not found for {pattern}")
        return "ERROR"

    filename = list_of_files[0]

    config = Config()
    config.LSTEventSource.apply_drs4_corrections = False
    config.LSTEventSource.pointing_information = False
    config.EventTimeCalculator.dragon_reference_time = int(counters["dragon_reference_time"])
    config.EventTimeCalculator.dragon_reference_counter = int(counters["dragon_reference_counter"])
    config.EventTimeCalculator.dragon_module_id = int(counters["dragon_reference_module_id"])

    try:
        with LSTEventSource(filename, config=config, max_events=n_events) as source:
            source.log.setLevel(logging.ERROR)

            event_type_counts = Counter(event.trigger.event_type for event in source)
            n_pedestals = event_type_counts[EventType.SKY_PEDESTAL]
            n_subarray = event_type_counts[EventType.SUBARRAY]

        if n_subarray / n_events > 0.999:
            run_type = "DRS4"
        elif n_pedestals / n_events > 0.1:
            run_type = "PEDCALIB"
        elif n_pedestals / n_events < 0.1:
            run_type = "DATA"
        else:
            run_type = "ERROR"

    except (AttributeError, ValueError, IOError, IndexError) as err:
        log.error(f"File {filename} has error: {err!r}")

        run_type = "ERROR"

    return run_type


def read_counters(date_path, run_number):
    """
    Get initial valid timestamps from the first subrun.
    Write down the reference Dragon module used, reference event_id.

    Parameters
    ----------
    date_path: pathlib.Path
        Directory that contains the R0 files
    run_number: int
        Number of the run

    Returns
    -------
    dict: reference counters and timestamps
    """
    pattern = date_path / f"LST-1.*.Run{run_number:05d}.0000*.fits.fz"
    try:
        f = MultiFiles(glob(str(pattern)))
        first_event = next(f)

        if first_event.event_id != 1:
            raise ValueError("Must be used on first file streams (subrun)")

        module_index = np.where(first_event.lstcam.module_status)[0][0]
        module_id = np.where(f.camera_config.lstcam.expected_modules_id == module_index)[0][0]
        dragon_counters = first_event.lstcam.counters.view(DRAGON_COUNTERS_DTYPE)
        dragon_reference_counter = combine_counters(
            dragon_counters["pps_counter"][module_index],
            dragon_counters["tenMHz_counter"][module_index],
        )

        ucts_available = bool(first_event.lstcam.extdevices_presence & 2)
        run_start = int(round(Time(f.camera_config.date, format="unix").unix_tai)) * int(1e9)

        if ucts_available:
            if int(f.camera_config.lstcam.idaq_version) > 37201:
                cdts = first_event.lstcam.cdts_data.view(CDTS_AFTER_37201_DTYPE)
            else:
                cdts = first_event.lstcam.cdts_data.view(CDTS_BEFORE_37201_DTYPE)

            ucts_timestamp = np.int64(cdts["timestamp"][0])
            dragon_reference_time = ucts_timestamp
            dragon_reference_source = "ucts"
        else:
            ucts_timestamp = np.int64(-1)
            dragon_reference_time = run_start
            dragon_reference_source = "run_start"

        return dict(
            ucts_timestamp=ucts_timestamp,
            run_start=run_start,
            dragon_reference_time=dragon_reference_time,
            dragon_reference_module_id=module_id,
            dragon_reference_module_index=module_index,
            dragon_reference_counter=dragon_reference_counter,
            dragon_reference_source=dragon_reference_source,
        )

    except Exception as err:
        log.error(f"Files {pattern} have error: {err}")

        return dict(
            ucts_timestamp=-1,
            run_start=-1,
            dragon_reference_time=-1,
            dragon_reference_module_id=-1,
            dragon_reference_module_index=-1,
            dragon_reference_counter=-1,
            dragon_reference_source=None,
        )


def main():
    """
    Build an astropy Table with run summary information and write it
    as ECSV file with the following information (one row per run):
    - run_id
    - number of subruns
    - type of run (DRS4, PEDCALIB, DATA, ERROR)
    - start of the run
    - dragon reference UCTS timestamp if available (-1 otherwise)
    - dragon reference time source ("ucts" or "run_date")
    - dragon_reference_module_id
    - dragon_reference_module_index
    - dragon_reference_counter
    """

    args = parser.parse_args()

    date_path = args.r0_path / args.date

    file_list = get_list_of_files(date_path)
    runs = get_list_of_runs(file_list)
    run_numbers, n_subruns = get_runs_and_subruns(runs)

    reference_counters = [read_counters(date_path, run) for run in run_numbers]

    run_types = [
        type_of_run(date_path, run, counters)
        for run, counters in zip(run_numbers, reference_counters)
    ]

    run_summary = Table(
        {
            col: np.array([d[col] for d in reference_counters], dtype=dtype)
            for col, dtype in dtypes.items()
        }
    )
    run_summary.meta["date"] = datetime.strptime(args.date, "%Y%m%d").date().isoformat()
    run_summary.meta["lstchain_version"] = __version__
    run_summary.add_column(run_numbers, name="run_id", index=0)
    run_summary.add_column(n_subruns, name="n_subruns", index=1)
    run_summary.add_column(run_types, name="run_type", index=2)
    run_summary.write(
        args.output_dir / f"RunSummary_{args.date}.ecsv", format="ascii.ecsv", delimiter=","
    )


if __name__ == "__main__":
    main()
