# Licensed under a 3-clause BSD style license - see LICENSE.rst
import pytest
import numpy as np
from numpy.testing import assert_allclose
import astropy.units as u
from astropy.table import Column, Table
from astropy.time import Time
from astropy.timeseries import BinnedTimeSeries, BoxLeastSquares
from gammapy.data import GTI
from gammapy.datasets import Datasets, FluxPointsDataset
from gammapy.datasets.actors import DatasetsActor
from gammapy.estimators import FluxPoints, LightCurveEstimator
from gammapy.estimators.points.lightcurve import parallel as parallel
from gammapy.estimators.points.tests.test_sed import (
    simulate_map_dataset,
    simulate_spectrum_dataset,
)
from gammapy.modeling import Fit
from gammapy.modeling.models import FoVBackgroundModel, PowerLawSpectralModel, SkyModel
from gammapy.utils.testing import (
    assert_time_allclose,
    mpl_plot_check,
    requires_data,
    requires_dependency,
)


@pytest.fixture(scope="session")
def lc():
    meta = dict(TIMESYS="utc", SED_TYPE="flux")

    table = Table(
        meta=meta,
        data=[
            Column(Time(["2010-01-01", "2010-01-03"]).mjd, "time_min"),
            Column(Time(["2010-01-03", "2010-01-10"]).mjd, "time_max"),
            Column([[1.0], [1.0]], "e_min", unit="TeV"),
            Column([[2.0], [2.0]], "e_max", unit="TeV"),
            Column([1e-11, 3e-11], "flux", unit="cm-2 s-1"),
            Column([0.1e-11, 0.3e-11], "flux_err", unit="cm-2 s-1"),
            Column([np.nan, 3.6e-11], "flux_ul", unit="cm-2 s-1"),
            Column([False, True], "is_ul"),
            Column([True, True], "success"),
        ],
    )
    gti = GTI.create("0h", "1h", "2010-01-01T00:00:00")

    return FluxPoints.from_table(table=table, gti=gti)


@pytest.fixture(scope="session")
def lc_2d():
    meta = dict(TIMESYS="utc", SED_TYPE="flux")

    table = Table(
        meta=meta,
        data=[
            Column(Time(["2010-01-01", "2010-01-03"]).mjd, "time_min"),
            Column(Time(["2010-01-03", "2010-01-10"]).mjd, "time_max"),
            Column([[1.0, 2.0], [1.0, 2.0]], "e_min", unit="TeV"),
            Column([[2.0, 4.0], [2.0, 4.0]], "e_max", unit="TeV"),
            Column([[1e-11, 1e-12], [3e-11, 3e-12]], "flux", unit="cm-2 s-1"),
            Column([[0.1e-11, 1e-13], [0.3e-11, 3e-13]], "flux_err", unit="cm-2 s-1"),
            Column([[np.nan, np.nan], [3.6e-11, 3.6e-12]], "flux_ul", unit="cm-2 s-1"),
            Column([[False, False], [True, True]], "is_ul"),
        ],
    )

    return FluxPoints.from_table(table=table)


def test_lightcurve_str(lc):
    info_str = str(lc)
    assert "time" in info_str


def test_lightcurve_properties_time(lc):
    axis = lc.geom.axes["time"]

    assert axis.reference_time.scale == "utc"
    assert axis.reference_time.format == "mjd"

    # Time-related attributes
    time = axis.time_mid
    assert time.scale == "utc"
    assert time.format == "mjd"
    assert_allclose(time.mjd, [55198, 55202.5])

    assert_allclose(axis.time_min.mjd, [55197, 55199])
    assert_allclose(axis.time_max.mjd, [55199, 55206])

    # Note: I'm not sure why the time delta has this scale and format
    time_delta = axis.time_delta
    assert time_delta.scale == "tai"
    assert time_delta.format == "jd"
    assert_allclose(time_delta.jd, [2, 7])


def test_lightcurve_properties_flux(lc):
    table = lc.to_table(sed_type="flux")
    flux = table["flux"].quantity
    assert flux.unit == "cm-2 s-1"
    assert_allclose(flux.value, [[1e-11], [3e-11]])


# TODO: extend these tests to cover other time scales.
# In those cases, CSV should not round-trip because there
# is no header info in CSV to store the time scale!


@pytest.mark.parametrize("sed_type", ["dnde", "flux", "likelihood"])
def test_lightcurve_read_write(tmp_path, lc, sed_type):
    lc.write(tmp_path / "tmp.fits", sed_type=sed_type)

    lc = FluxPoints.read(tmp_path / "tmp.fits")
    assert_allclose(lc.gti.time_start[0].mjd, 55197.000766, rtol=1e-5)

    # Check if time-related info round-trips
    axis = lc.geom.axes["time"]
    assert axis.reference_time.scale == "utc"
    assert axis.reference_time.format == "mjd"
    assert_allclose(axis.time_mid.mjd, [55198, 55202.5])


def test_lightcurve_plot(lc, lc_2d):
    with mpl_plot_check():
        lc.plot()

    with mpl_plot_check():
        lc.plot(marker="o", time_format="mjd")

    with mpl_plot_check():
        lc_2d.plot(axis_name="time")


@requires_data()
def test_lightcurve_to_time_series():
    from gammapy.catalog import SourceCatalog4FGL

    catalog_4fgl = SourceCatalog4FGL("$GAMMAPY_DATA/catalogs/fermi/gll_psc_v20.fit.gz")
    lightcurve = catalog_4fgl["FGES J1553.8-5325"].lightcurve()

    table = lightcurve.to_table(sed_type="flux", format="binned-time-series")

    timeseries = BinnedTimeSeries(data=table)

    assert_allclose(timeseries.time_bin_center.mjd[0], 54863.97885336907)
    assert_allclose(timeseries.time_bin_end.mjd[0], 55045.301668796295)

    time_axis = lightcurve.geom.axes["time"]
    assert_allclose(timeseries.time_bin_end.mjd[0], time_axis.time_max.mjd[0])

    # assert that it interfaces with periodograms

    p = BoxLeastSquares.from_timeseries(
        timeseries=timeseries, signal_column_name="flux", uncertainty="flux_errp"
    )

    result = p.power(1 * u.year, 0.5 * u.year)
    assert_allclose(result.duration, 182.625 * u.d)


def get_spectrum_datasets():
    model = SkyModel(spectral_model=PowerLawSpectralModel())
    dataset_1 = simulate_spectrum_dataset(model=model, random_state=0)
    dataset_1._name = "dataset_1"
    gti1 = GTI.create("0h", "1h", Time("2010-01-01T00:00:00").tt)
    dataset_1.gti = gti1

    dataset_2 = simulate_spectrum_dataset(model=model, random_state=1)
    dataset_2._name = "dataset_2"
    gti2 = GTI.create("1h", "2h", Time("2010-01-01T00:00:00").tt)
    dataset_2.gti = gti2

    return [dataset_1, dataset_2]


@requires_data()
def test_group_datasets_in_time_interval():
    # Doing a LC on one hour bin
    datasets = Datasets(get_spectrum_datasets())
    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T01:00:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
    ]

    group_table = datasets.gti.group_table(time_intervals)

    assert len(group_table) == 2
    assert_allclose(group_table["time_min"], [55197.0, 55197.04166666667])
    assert_allclose(group_table["time_max"], [55197.04166666667, 55197.083333333336])
    assert_allclose(group_table["group_idx"], [0, 1])


@requires_data()
def test_group_datasets_in_time_interval_outflows():
    datasets = Datasets(get_spectrum_datasets())
    # Check Overflow
    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T00:55:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
    ]

    group_table = datasets.gti.group_table(time_intervals)
    assert group_table["bin_type"][0] == "overflow"

    # Check underflow
    time_intervals = [
        Time(["2010-01-01T00:05:00", "2010-01-01T01:00:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
    ]
    group_table = datasets.gti.group_table(time_intervals)
    assert group_table["bin_type"][0] == "underflow"


@requires_data()
def test_lightcurve_estimator_fit_options():
    # Doing a LC on one hour bin
    datasets = get_spectrum_datasets()
    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T01:00:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
    ]
    energy_edges = [1, 30] * u.TeV

    estimator = LightCurveEstimator(
        energy_edges=energy_edges,
        time_intervals=time_intervals,
        selection_optional="all",
        fit=Fit(backend="minuit", optimize_opts=dict(tol=0.2, strategy=1)),
        norm=dict(scan_n_values=3),
    )

    assert_allclose(estimator.fit.optimize_opts["tol"], 0.2)

    result = estimator.fit.run(datasets=datasets)
    assert_allclose(result.minuit.tol, 0.2)


@requires_data()
def test_lightcurve_estimator_spectrum_datasets():
    # Doing a LC on one hour bin
    datasets = get_spectrum_datasets()
    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T01:00:00"]).tt,
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]).tt,
    ]

    estimator = LightCurveEstimator(
        energy_edges=[1, 30] * u.TeV,
        time_intervals=time_intervals,
        selection_optional="all",
    )
    estimator.norm.scan_n_values = 3

    lightcurve = estimator.run(datasets)
    table = lightcurve.to_table()
    assert_allclose(table["time_min"], [55197.0, 55197.041667])
    assert_allclose(table["time_max"], [55197.041667, 55197.083333])
    assert_allclose(table["e_ref"], [[5.623413], [5.623413]])
    assert_allclose(table["e_min"], [[1], [1]])
    assert_allclose(table["e_max"], [[31.622777], [31.622777]])
    assert_allclose(table["ref_dnde"], [[3.162278e-14], [3.162278e-14]], rtol=1e-5)
    assert_allclose(table["ref_flux"], [[9.683772e-13], [9.683772e-13]], rtol=1e-5)
    assert_allclose(table["ref_eflux"], [[3.453878e-12], [3.453878e-12]], rtol=1e-5)
    assert_allclose(table["stat"], [[16.824042], [17.391981]], rtol=1e-5)
    assert_allclose(table["norm"], [[0.911963], [0.9069318]], rtol=1e-2)
    assert_allclose(table["norm_err"], [[0.057769], [0.057835]], rtol=1e-2)
    assert_allclose(table["counts"], [[[791, np.nan]], [[np.nan, 784]]])
    assert_allclose(table["norm_errp"], [[0.058398], [0.058416]], rtol=1e-2)
    assert_allclose(table["norm_errn"], [[0.057144], [0.057259]], rtol=1e-2)
    assert_allclose(table["norm_ul"], [[1.029989], [1.025061]], rtol=1e-2)
    assert_allclose(table["sqrt_ts"], [[19.384781], [19.161769]], rtol=1e-2)
    assert_allclose(table["ts"], [[375.769735], [367.173374]], rtol=1e-2)
    assert_allclose(table[0]["norm_scan"], [[0.2, 1.0, 5.0]])
    assert_allclose(
        table[0]["stat_scan"],
        [[224.058304, 19.074405, 2063.75636]],
        rtol=1e-5,
    )
    assert table.meta["TIMESYS"] == "tt"
    assert table.meta["TIMEUNIT"] == "d"
    assert table.meta["MJDREFF"] == 0
    assert table.meta["MJDREFI"] == 0

    # TODO: fix reference model I/O
    fp = FluxPoints.from_table(table=table, reference_model=PowerLawSpectralModel())
    assert fp.norm.geom.axes.names == ["energy", "time"]
    assert fp.counts.geom.axes.names == ["dataset", "energy", "time"]
    assert fp.stat_scan.geom.axes.names == ["norm", "energy", "time"]
    assert_time_allclose(
        fp.geom.axes["time"].time_min, lightcurve.geom.axes["time"].time_min
    )
    assert_time_allclose(
        fp.geom.axes["time"].time_max, lightcurve.geom.axes["time"].time_max
    )


@requires_data()
def test_lightcurve_estimator_spectrum_datasets_2_energy_bins():
    # Doing a LC on one hour bin
    datasets = get_spectrum_datasets()
    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T01:00:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
    ]

    estimator = LightCurveEstimator(
        energy_edges=[1, 5, 30] * u.TeV,
        time_intervals=time_intervals,
        selection_optional="all",
    )
    estimator.norm.scan_n_values = 3
    lightcurve = estimator.run(datasets)
    table = lightcurve.to_table()

    assert_allclose(table["time_min"], [55197.0, 55197.041667])
    assert_allclose(table["time_max"], [55197.041667, 55197.083333])
    assert_allclose(table["e_ref"], [[2.238721, 12.589254], [2.238721, 12.589254]])
    assert_allclose(table["e_min"], [[1, 5.011872], [1, 5.011872]])
    assert_allclose(table["e_max"], [[5.011872, 31.622777], [5.011872, 31.622777]])
    assert_allclose(
        table["ref_dnde"],
        [[1.995262e-13, 6.309573e-15], [1.995262e-13, 6.309573e-15]],
        rtol=1e-5,
    )
    assert_allclose(
        table["stat"],
        [[8.234951, 8.30321], [2.037205, 15.300507]],
        rtol=1e-5,
    )
    assert_allclose(
        table["norm"],
        [[0.894723, 0.967419], [0.914283, 0.882351]],
        rtol=1e-2,
    )
    assert_allclose(
        table["norm_err"],
        [[0.065905, 0.121288], [0.06601, 0.119457]],
        rtol=1e-2,
    )
    assert_allclose(
        table["counts"],
        [[[669.0, np.nan], [122.0, np.nan]], [[np.nan, 667.0], [np.nan, 117.0]]],
    )
    assert_allclose(
        table["norm_errp"],
        [[0.06664, 0.124741], [0.066815, 0.122832]],
        rtol=1e-2,
    )
    assert_allclose(
        table["norm_errn"],
        [[0.065176, 0.117904], [0.065212, 0.116169]],
        rtol=1e-2,
    )
    assert_allclose(
        table["norm_ul"],
        [[1.029476, 1.224117], [1.049283, 1.134874]],
        rtol=1e-2,
    )
    assert_allclose(
        table["sqrt_ts"],
        [[16.233236, 10.608376], [16.609784, 9.557339]],
        rtol=1e-2,
    )
    assert_allclose(table[0]["norm_scan"], [[0.2, 1.0, 5.0], [0.2, 1.0, 5.0]])
    assert_allclose(
        table[0]["stat_scan"],
        [[153.880281, 10.701492, 1649.609684], [70.178023, 8.372913, 414.146676]],
        rtol=1e-5,
    )

    # those quantities are currently not part of the table so we test separately
    npred = lightcurve.npred.data.squeeze()
    assert_allclose(
        npred,
        [[[669.36, np.nan], [121.66, np.nan]], [[np.nan, 664.41], [np.nan, 115.09]]],
        rtol=1e-3,
    )

    npred_excess_err = lightcurve.npred_excess_err.data.squeeze()
    assert_allclose(
        npred_excess_err,
        [[[26.80, np.nan], [11.31, np.nan]], [[np.nan, 26.85], [np.nan, 11.14]]],
        rtol=1e-3,
    )

    npred_excess_errp = lightcurve.npred_excess_errp.data.squeeze()
    assert_allclose(
        npred_excess_errp,
        [[[27.11, np.nan], [11.63, np.nan]], [[np.nan, 27.15], [np.nan, 11.46]]],
        rtol=1e-3,
    )

    npred_excess_errn = lightcurve.npred_excess_errn.data.squeeze()
    assert_allclose(
        npred_excess_errn,
        [[[26.50, np.nan], [11.00, np.nan]], [[np.nan, 26.54], [np.nan, 10.84]]],
        rtol=1e-3,
    )

    npred_excess_ul = lightcurve.npred_excess_ul.data.squeeze()
    assert_allclose(
        npred_excess_ul,
        [[[418.68, np.nan], [114.19, np.nan]], [[np.nan, 426.74], [np.nan, 105.86]]],
        rtol=1e-3,
    )

    fp = FluxPoints.from_table(table=table, reference_model=PowerLawSpectralModel())
    assert fp.norm.geom.axes.names == ["energy", "time"]
    assert fp.counts.geom.axes.names == ["dataset", "energy", "time"]
    assert fp.stat_scan.geom.axes.names == ["norm", "energy", "time"]


@requires_data()
def test_lightcurve_estimator_spectrum_datasets_with_mask_fit():
    # Doing a LC on one hour bin
    datasets = get_spectrum_datasets()
    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T01:00:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
    ]

    energy_min_fit = 1 * u.TeV
    energy_max_fit = 3 * u.TeV
    for dataset in datasets:
        geom = dataset.counts.geom
        dataset.mask_fit = geom.energy_mask(
            energy_min=energy_min_fit, energy_max=energy_max_fit
        )

    selection = ["scan"]
    estimator = LightCurveEstimator(
        energy_edges=[1, 30] * u.TeV,
        time_intervals=time_intervals,
        selection_optional=selection,
    )
    estimator.norm.scan_n_values = 3
    lightcurve = estimator.run(datasets)
    table = lightcurve.to_table()
    assert_allclose(table["time_min"], [55197.0, 55197.041667])
    assert_allclose(table["time_max"], [55197.041667, 55197.083333])
    assert_allclose(table["stat"], [[6.603043], [0.421051]], rtol=1e-3)
    assert_allclose(table["norm"], [[0.885124], [0.967054]], rtol=1e-3)


@requires_data()
def test_lightcurve_estimator_spectrum_datasets_default():
    # Test default time interval: each time interval is equal to the gti of each
    # dataset, here one hour
    datasets = get_spectrum_datasets()
    selection = ["scan"]
    estimator = LightCurveEstimator(
        energy_edges=[1, 30] * u.TeV, selection_optional=selection
    )
    estimator.norm.scan_n_values = 3
    lightcurve = estimator.run(datasets)
    table = lightcurve.to_table()
    assert_allclose(table["time_min"], [55197.0, 55197.041667])
    assert_allclose(table["time_max"], [55197.041667, 55197.083333])
    assert_allclose(table["norm"], [[0.911963], [0.906931]], rtol=1e-3)


@requires_data()
def test_lightcurve_estimator_spectrum_datasets_notordered():
    # Test that if the time intervals given are not ordered in time, it is first ordered
    # correctly and then compute as expected
    datasets = get_spectrum_datasets()
    time_intervals = [
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
        Time(["2010-01-01T00:00:00", "2010-01-01T01:00:00"]),
    ]
    estimator = LightCurveEstimator(
        energy_edges=[1, 100] * u.TeV,
        time_intervals=time_intervals,
        selection_optional=["scan"],
    )
    estimator.norm.scan_n_values = 3
    lightcurve = estimator.run(datasets)
    table = lightcurve.to_table()
    assert_allclose(table["time_min"], [55197.0, 55197.041667])
    assert_allclose(table["time_max"], [55197.041667, 55197.083333])
    assert_allclose(table["norm"], [[0.911963], [0.906931]], rtol=1e-3)


@requires_data()
def test_lightcurve_estimator_spectrum_datasets_largerbin():
    # Test all dataset in a single LC bin, here two hours
    datasets = get_spectrum_datasets()
    time_intervals = [Time(["2010-01-01T00:00:00", "2010-01-01T02:00:00"])]
    estimator = LightCurveEstimator(
        energy_edges=[1, 30] * u.TeV,
        time_intervals=time_intervals,
        selection_optional=["scan"],
    )
    estimator.norm.scan_n_values = 3
    lightcurve = estimator.run(datasets)
    table = lightcurve.to_table()

    assert_allclose(table["time_min"], [55197.0])
    assert_allclose(table["time_max"], [55197.083333])
    assert_allclose(table["e_ref"][0], [5.623413])
    assert_allclose(table["e_min"][0], [1])
    assert_allclose(table["e_max"][0], [31.622777])
    assert_allclose(table["ref_dnde"][0], [3.162278e-14], rtol=1e-5)
    assert_allclose(table["ref_flux"][0], [9.683772e-13], rtol=1e-5)
    assert_allclose(table["ref_eflux"][0], [3.453878e-12], rtol=1e-5)
    assert_allclose(table["stat"][0], [34.219808], rtol=1e-5)
    assert_allclose(table["norm"][0], [0.909646], rtol=1e-5)
    assert_allclose(table["norm_err"][0], [0.040874], rtol=1e-3)
    assert_allclose(table["ts"][0], [742.939324], rtol=1e-4)


@requires_data()
def test_lightcurve_estimator_spectrum_datasets_emptybin():
    # Test all dataset in a single LC bin, here two hours
    datasets = get_spectrum_datasets()
    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T02:00:00"]),
        Time(["2010-02-01T00:00:00", "2010-02-01T02:00:00"]),
    ]
    estimator = LightCurveEstimator(
        energy_edges=[1, 30] * u.TeV,
        time_intervals=time_intervals,
        selection_optional=["scan"],
    )
    estimator.norm.scan_n_values = 3
    lightcurve = estimator.run(datasets)
    table = lightcurve.to_table()

    assert_allclose(table["time_min"], [55197.0])
    assert_allclose(table["time_max"], [55197.083333])


@requires_data()
def test_lightcurve_estimator_spectrum_datasets_timeoverlaped():
    # Check that it returns a ValueError if the time intervals overlapped
    datasets = get_spectrum_datasets()
    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T01:30:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
    ]
    with pytest.raises(ValueError) as excinfo:
        estimator = LightCurveEstimator(time_intervals=time_intervals)
        estimator.norm.scan_n_values = 3
        estimator.run(datasets)

    msg = "Overlapping time bins"
    assert str(excinfo.value) == msg


@requires_data()
def test_lightcurve_estimator_spectrum_datasets_gti_not_include_in_time_intervals():
    # Check that it returns a ValueError if the time intervals are smaller than the dataset GTI.
    datasets = get_spectrum_datasets()
    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T00:05:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T01:05:00"]),
    ]
    estimator = LightCurveEstimator(
        energy_edges=[1, 30] * u.TeV,
        time_intervals=time_intervals,
        selection_optional=["scan"],
    )
    estimator.norm.scan_n_values = 3
    with pytest.raises(ValueError) as excinfo:
        estimator.run(datasets)
    msg = "LightCurveEstimator: No datasets in time intervals"
    assert str(excinfo.value) == msg


def get_map_datasets():
    dataset_1 = simulate_map_dataset(random_state=0, name="dataset_1")
    gti1 = GTI.create("0 h", "1 h", "2010-01-01T00:00:00")
    dataset_1.gti = gti1

    dataset_2 = simulate_map_dataset(random_state=1, name="dataset_2")
    gti2 = GTI.create("1 h", "2 h", "2010-01-01T00:00:00")
    dataset_2.gti = gti2

    model = dataset_1.models["source"].copy(name="test_source")
    bkg_model_1 = FoVBackgroundModel(dataset_name="dataset_1")
    bkg_model_2 = FoVBackgroundModel(dataset_name="dataset_2")

    dataset_1.models = [model, bkg_model_1]
    dataset_2.models = [model, bkg_model_2]

    return [dataset_1, dataset_2]


@requires_data()
def test_lightcurve_estimator_map_datasets():
    datasets = get_map_datasets()

    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T01:00:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
    ]
    estimator = LightCurveEstimator(
        energy_edges=[1, 100] * u.TeV,
        source="test_source",
        time_intervals=time_intervals,
        selection_optional=["scan"],
    )
    lightcurve = estimator.run(datasets)
    table = lightcurve.to_table()
    assert_allclose(table["time_min"], [55197.0, 55197.041667])
    assert_allclose(table["time_max"], [55197.041667, 55197.083333])
    assert_allclose(table["e_ref"], [[10.857111], [10.857111]])
    assert_allclose(table["e_min"], [[1.178769], [1.178769]], rtol=1e-5)
    assert_allclose(table["e_max"], [[100], [100]])
    assert_allclose(table["ref_dnde"], [[8.483429e-14], [8.483429e-14]], rtol=1e-5)
    assert_allclose(table["ref_flux"], [[8.383429e-12], [8.383429e-12]], rtol=1e-5)
    assert_allclose(table["ref_eflux"], [[4.4407e-11], [4.4407e-11]], rtol=1e-5)
    assert_allclose(table["stat"], [[9402.778975], [9517.750207]], rtol=1e-2)
    assert_allclose(table["norm"], [[0.971592], [0.963286]], rtol=1e-2)
    assert_allclose(table["norm_err"], [[0.044643], [0.044475]], rtol=1e-2)
    assert_allclose(table["sqrt_ts"], [[35.880361], [35.636547]], rtol=1e-2)
    assert_allclose(table["ts"], [[1287.4003], [1269.963491]], rtol=1e-2)

    datasets = get_map_datasets()

    time_intervals2 = [Time(["2010-01-01T00:00:00", "2010-01-01T02:00:00"])]
    estimator2 = LightCurveEstimator(
        energy_edges=[1, 100] * u.TeV,
        source="test_source",
        time_intervals=time_intervals2,
        selection_optional=["scan"],
    )
    lightcurve2 = estimator2.run(datasets)
    table = lightcurve2.to_table()
    assert_allclose(table["time_min"][0], [55197.0])
    assert_allclose(table["time_max"][0], [55197.083333])
    assert_allclose(table["e_ref"][0], [10.857111], rtol=1e-5)
    assert_allclose(table["e_min"][0], [1.178769], rtol=1e-5)
    assert_allclose(table["e_max"][0], [100])
    assert_allclose(table["ref_dnde"][0], [8.483429e-14], rtol=1e-5)
    assert_allclose(table["ref_flux"][0], [8.383429e-12], rtol=1e-5)
    assert_allclose(table["ref_eflux"][0], [4.4407e-11], rtol=1e-5)
    assert_allclose(table["stat"][0], [18920.54651], rtol=1e-2)
    assert_allclose(table["norm"][0], [0.967438], rtol=1e-2)
    assert_allclose(table["norm_err"][0], [0.031508], rtol=1e-2)
    assert_allclose(table["counts"][0], [[2205, 2220]])
    assert_allclose(table["ts"][0], [2557.346464], rtol=1e-2)


@requires_data()
@requires_dependency("ray")
def test_lightcurve_estimator_map_datasets_ray_actors():
    datasets = get_map_datasets()

    datasets = DatasetsActor(datasets)

    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T01:00:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
    ]
    estimator = LightCurveEstimator(
        energy_edges=[1, 100] * u.TeV,
        source="test_source",
        time_intervals=time_intervals,
        selection_optional=["scan"],
    )
    lightcurve = estimator.run(datasets)
    table = lightcurve.to_table()
    assert_allclose(table["time_min"], [55197.0, 55197.041667])
    assert_allclose(table["time_max"], [55197.041667, 55197.083333])
    assert_allclose(table["e_ref"], [[10.857111], [10.857111]])
    assert_allclose(table["e_min"], [[1.178769], [1.178769]], rtol=1e-5)
    assert_allclose(table["e_max"], [[100], [100]])
    assert_allclose(table["ref_dnde"], [[8.483429e-14], [8.483429e-14]], rtol=1e-5)
    assert_allclose(table["ref_flux"], [[8.383429e-12], [8.383429e-12]], rtol=1e-5)
    assert_allclose(table["ref_eflux"], [[4.4407e-11], [4.4407e-11]], rtol=1e-5)
    assert_allclose(table["stat"], [[9402.778975], [9517.750207]], rtol=1e-2)
    assert_allclose(table["norm"], [[0.971592], [0.963286]], rtol=1e-2)
    assert_allclose(table["norm_err"], [[0.044643], [0.044475]], rtol=1e-2)
    assert_allclose(table["sqrt_ts"], [[35.880361], [35.636547]], rtol=1e-2)
    assert_allclose(table["ts"], [[1287.4003], [1269.963491]], rtol=1e-2)

    datasets = get_map_datasets()

    time_intervals2 = [Time(["2010-01-01T00:00:00", "2010-01-01T02:00:00"])]
    estimator2 = LightCurveEstimator(
        energy_edges=[1, 100] * u.TeV,
        source="test_source",
        time_intervals=time_intervals2,
        selection_optional=["scan"],
    )
    lightcurve2 = estimator2.run(datasets)
    table = lightcurve2.to_table()
    assert_allclose(table["time_min"][0], [55197.0])
    assert_allclose(table["time_max"][0], [55197.083333])
    assert_allclose(table["e_ref"][0], [10.857111], rtol=1e-5)
    assert_allclose(table["e_min"][0], [1.178769], rtol=1e-5)
    assert_allclose(table["e_max"][0], [100])
    assert_allclose(table["ref_dnde"][0], [8.483429e-14], rtol=1e-5)
    assert_allclose(table["ref_flux"][0], [8.383429e-12], rtol=1e-5)
    assert_allclose(table["ref_eflux"][0], [4.4407e-11], rtol=1e-5)
    assert_allclose(table["stat"][0], [18920.54651], rtol=1e-2)
    assert_allclose(table["norm"][0], [0.967438], rtol=1e-2)
    assert_allclose(table["norm_err"][0], [0.031508], rtol=1e-2)
    assert_allclose(table["counts"][0], [[2205, 2220]])
    assert_allclose(table["ts"][0], [2557.346464], rtol=1e-2)


@requires_data()
def test_recompute_ul():
    datasets = get_spectrum_datasets()
    selection = ["all"]
    estimator = LightCurveEstimator(
        energy_edges=[1, 3, 10, 30] * u.TeV, selection_optional=selection, n_sigma_ul=2
    )
    lightcurve = estimator.run(datasets)

    assert_allclose(
        lightcurve.dnde_ul.data[0],
        [[[3.26070325e-13]], [[3.82484628e-14]], [[4.16433528e-15]]],
        rtol=1e-3,
    )

    new_lightcurve = lightcurve.recompute_ul(n_sigma_ul=4)

    assert_allclose(
        new_lightcurve.dnde_ul.data[0],
        [[[3.75813611e-13]], [[4.63590079e-14]], [[5.64634904e-15]]],
        rtol=1e-3,
    )

    assert new_lightcurve.meta["n_sigma_ul"] == 4

    # test if scan is not present
    selection = []
    estimator = LightCurveEstimator(
        energy_edges=[1, 30] * u.TeV, selection_optional=selection
    )
    lightcurve = estimator.run(datasets)
    with pytest.raises(ValueError):
        lightcurve.recompute_ul(n_sigma_ul=4)


@requires_data()
def test_dataset_stat_type():
    datasets = get_spectrum_datasets()
    selection = ["all"]
    estimator = LightCurveEstimator(
        energy_edges=[1, 3, 30] * u.TeV, selection_optional=selection, n_sigma_ul=2
    )
    lightcurve = estimator.run(datasets)
    assert_allclose(
        lightcurve.dnde_ul.data[0], [[[3.260703e-13]], [[1.159354e-14]]], rtol=1e-3
    )

    lightcurve_dataset = FluxPointsDataset(
        data=lightcurve, models=[lightcurve.reference_model]
    )
    lightcurve_dataset.stat_type = "profile"
    assert_allclose(lightcurve_dataset.stat_sum(), 5.741539, rtol=1e-5)
    lightcurve_dataset.stat_type = "distrib"
    assert_allclose(lightcurve_dataset.stat_sum(), 5.824218, rtol=1e-5)
    lightcurve_dataset.stat_type = "chi2"
    assert_allclose(lightcurve_dataset.stat_sum(), 6.014128, rtol=1e-5)

    # test if scan is not present
    selection = []
    estimator = LightCurveEstimator(
        energy_edges=[1, 30] * u.TeV, selection_optional=selection
    )
    lightcurve = estimator.run(datasets)

    lightcurve_dataset = FluxPointsDataset(
        data=lightcurve, models=[lightcurve.reference_model]
    )
    with pytest.raises(AttributeError):
        lightcurve_dataset.stat_type = "profile"
        lightcurve_dataset.stat_sum()


@requires_data()
def test_lightcurve_parallel_multiprocessing():
    datasets = get_spectrum_datasets()
    selection = ["all"]
    estimator = LightCurveEstimator(
        energy_edges=[1, 3, 30] * u.TeV,
        selection_optional=selection,
        n_sigma_ul=2,
        n_jobs=2,
    )
    assert estimator.n_jobs == 2
    lightcurve = estimator.run(datasets)
    assert_allclose(
        lightcurve.dnde_ul.data[0], [[[3.260703e-13]], [[1.159354e-14]]], rtol=1e-3
    )
    assert estimator._get_n_child_jobs == 1

    parallel.ALLOW_CHILD_JOBS = True
    lightcurve = estimator.run(datasets)
    assert_allclose(
        lightcurve.dnde_ul.data[0], [[[3.260703e-13]], [[1.159354e-14]]], rtol=1e-3
    )
    assert estimator._get_n_child_jobs == 2
    parallel.ALLOW_CHILD_JOBS = False


@requires_data()
@requires_dependency("ray")
def test_lightcurve_parallel_ray():
    datasets = get_spectrum_datasets()
    selection = ["all"]

    estimator = LightCurveEstimator(
        energy_edges=[1, 3, 30] * u.TeV,
        selection_optional=selection,
        n_sigma_ul=2,
        n_jobs=2,
        parallel_backend="ray",
    )

    assert estimator.n_jobs == 2
    lightcurve = estimator.run(datasets)
    assert_allclose(
        lightcurve.dnde_ul.data[0], [[[3.260703e-13]], [[1.159354e-14]]], rtol=1e-3
    )
    assert estimator._get_n_child_jobs == 1

    parallel.ALLOW_CHILD_JOBS = True
    lightcurve = estimator.run(datasets)
    assert_allclose(
        lightcurve.dnde_ul.data[0], [[[3.260703e-13]], [[1.159354e-14]]], rtol=1e-3
    )
    assert estimator._get_n_child_jobs == 2
    parallel.ALLOW_CHILD_JOBS = False


@requires_data()
def test_lightcurve_stacking():
    datasets = get_spectrum_datasets()
    time_intervals = [
        Time(["2010-01-01T00:00:00", "2010-01-01T01:00:00"]),
        Time(["2010-01-01T01:00:00", "2010-01-01T02:00:00"]),
    ]
    energy_edges = [1, 10] * u.TeV

    # Internal stacking
    estimator_stack = LightCurveEstimator(
        energy_edges=energy_edges,
        time_intervals=time_intervals,
        stack_over_time_interval=True,
    )
    results_stack = estimator_stack.run(datasets)
    table_stack = results_stack.to_table()

    # External stacking
    datasets = Datasets(datasets)
    models = datasets.models
    stacked_datasets = Datasets()

    for t_min, t_max in time_intervals:
        selected = datasets.select_time(time_min=t_min, time_max=t_max, atol="1e-6 s")
        stacked = selected.stack_reduce()
        stacked_datasets.append(stacked)

    stacked_datasets.models = models
    estimator = LightCurveEstimator(
        energy_edges=energy_edges,
        time_intervals=time_intervals,
        stack_over_time_interval=False,
    )
    results_stacked = estimator.run(stacked_datasets)
    table_stack_outside = results_stacked.to_table()
    assert_allclose(table_stack["stat_null"], table_stack_outside["stat_null"])
