import os
import cProfile
import numpy as np
from unittest import TestCase
from dataclasses import fields, MISSING
from eos import options, reduction_reflectivity, logconfig
from orsopy import fileio

logconfig.setup_logging()
logconfig.update_loglevel(1)

# TODO: add unit tests for individual parts of reduction

class FullAmorTest(TestCase):
    @classmethod
    def setUpClass(cls):
        # generate map for option defaults
        cls._field_defaults = {}
        for opt in [options.ExperimentConfig, options.ReflectivityReductionConfig, options.ReflectivityOutputConfig]:
            defaults = {}
            for field in fields(opt):
                if field.default not in [None, MISSING]:
                    defaults[field.name] = field.default
                elif field.default_factory not in [None, MISSING]:
                    defaults[field.name] = field.default_factory()
            cls._field_defaults[opt.__name__] = defaults
        cls.pr = cProfile.Profile()

    @classmethod
    def tearDownClass(cls):
        cls.pr.dump_stats("profile_test.prof")

    def setUp(self):
        self.pr.enable()
        self.reader_config = options.ReaderConfig(
                year=2025,
                rawPath=["test_data"],
                )

    def tearDown(self):
        self.pr.disable()
        for fi in ['test_results/test.Rqz.ort', 'test_results/5952.norm']:
           try:
               os.unlink(fi)
           except FileNotFoundError:
               pass

    def test_time_slicing(self):
        experiment_config = options.ExperimentConfig(
                chopperPhase=-13.5,
                chopperPhaseOffset=-5,
                yRange=(18, 48),
                lambdaRange=(3., 11.5),
                mu=0,
                nu=0,
                muOffset=0.0,
                sampleModel='air | 10 H2O | D2O'
                )
        reduction_config = options.ReflectivityReductionConfig(
                qResolution=0.01,
                qzRange=(0.01, 0.15),
                thetaRange=(-0.75, 0.75),
                fileIdentifier=["6003-6005"],
                scale=[1],
                normalisationFileIdentifier=[],
                timeSlize=[300.0]
                )
        output_config = options.ReflectivityOutputConfig(
                outputFormats=[options.OutputFomatOption.Rqz_ort],
                outputName='test',
                outputPath='test_results',
                )
        config=options.ReflectivityConfig(self.reader_config, experiment_config, reduction_config, output_config)
        # run three times to get similar timing to noslicing runs
        reducer = reduction_reflectivity.ReflectivityReduction(config)
        reducer.reduce()
        reducer = reduction_reflectivity.ReflectivityReduction(config)
        reducer.reduce()
        reducer = reduction_reflectivity.ReflectivityReduction(config)
        reducer.reduce()

    def test_noslicing(self):
        experiment_config = options.ExperimentConfig(
                chopperPhase=-13.5,
                chopperPhaseOffset=-5,
                yRange=(18, 48),
                lambdaRange=(3., 11.5),
                mu=0,
                nu=0,
                muOffset=0.0,
                )
        reduction_config = options.ReflectivityReductionConfig(
                qResolution=0.01,
                thetaRange=(-0.75, 0.75),
                fileIdentifier=["6003", "6004", "6005"],
                scale=[1],
                normalisationFileIdentifier=["5952"],
                autoscale=(0.0, 0.05),
                )
        output_config = options.ReflectivityOutputConfig(
                outputFormats=[options.OutputFomatOption.Rqz_ort],
                outputName='test',
                outputPath='test_results',
                )
        config=options.ReflectivityConfig(self.reader_config, experiment_config, reduction_config, output_config)
        reducer = reduction_reflectivity.ReflectivityReduction(config)
        reducer.reduce()
        # run second time to reuse norm file
        reducer = reduction_reflectivity.ReflectivityReduction(config)
        reducer.reduce()

    def test_eventfilter(self):
        self.reader_config.year = 2026
        experiment_config = options.ExperimentConfig()
        reduction_config = options.ReflectivityReductionConfig(fileIdentifier=["826"],
                                                               logfilter=['polarization_config_label==2'])
        output_config = options.ReflectivityOutputConfig(
                outputFormats=[options.OutputFomatOption.Rqz_ort],
                outputName='test',
                outputPath='test_results',
                )
        config=options.ReflectivityConfig(self.reader_config, experiment_config, reduction_config, output_config)
        reducer = reduction_reflectivity.ReflectivityReduction(config)
        reducer.reduce()
        espin_up = reducer.dataset.data.events.shape[0]

        reduction_config.logfilter = ['polarization_config_label==3']
        output_config.append = True
        reducer = reduction_reflectivity.ReflectivityReduction(config)
        reducer.reduce()
        espin_down = reducer.dataset.data.events.shape[0]
        # measurement should have about 2x as many counts in spin_down
        self.assertAlmostEqual(espin_down/espin_up, 2., 2)

        # perform the same filter but remove pulses during which the switch occured
        reduction_config.logfilter = ['!polarization_config_label==3']
        output_config.append = True
        reducer = reduction_reflectivity.ReflectivityReduction(config)
        reducer.reduce()
        espin_down2 = reducer.dataset.data.events.shape[0]
        # measurement should have about 2x as many counts in spin_down
        self.assertLess(espin_down2, espin_down)

    def test_polsplitting(self):
        self.reader_config.year = 2026
        experiment_config = options.ExperimentConfig()
        reduction_config = options.ReflectivityReductionConfig(fileIdentifier=["826"])
        output_config = options.ReflectivityOutputConfig(
                outputFormats=[options.OutputFomatOption.Rqz_ort],
                outputName='test',
                outputPath='test_results',
                )
        config=options.ReflectivityConfig(self.reader_config, experiment_config, reduction_config, output_config)
        reducer = reduction_reflectivity.ReflectivityReduction(config)
        reducer.reduce()

        results = fileio.load_orso(os.path.join(output_config.outputPath, output_config.outputName+'.Rqz.ort'))
        self.assertEqual(len(results), 2)
        self.assertEqual(results[0].info.data_source.measurement.instrument_settings.polarization, 'po')
        self.assertEqual(results[1].info.data_source.measurement.instrument_settings.polarization, 'mo')
        espin_up = np.nansum(results[0].data[:,1])
        espin_down = np.nansum(results[1].data[:,1])
        # the total intensity should be around equal as events are doubled and monitor counts are doubled
        self.assertAlmostEqual(espin_down/espin_up, 1., 2)
