using Test
using XLSX: readxlsx
using DataFrames
using Mimi
using MimiDICE2013
using CSVFiles

using MimiDICE2013: getparams

@testset "MimiDICE2013" begin

    # ------------------------------------------------------------------------------
    #   1. Run tests on the whole model
    # ------------------------------------------------------------------------------

    @testset "MimiDICE2013-model" begin

        m = MimiDICE2013.get_model();
        run(m)

        f = readxlsx(joinpath(dirname(@__FILE__), "..", "data", "DICE_2013_Excel.xlsm"))

        # Test Precision
        Precision = 1.0e-11

        # Time Periods
        T = 60

        # TATM Test (temperature increase)
        True_TATM = getparams(f, "B99:BI99", :all, "Base", T);
        @test maximum(abs, m[:climatedynamics, :TATM] .- True_TATM) ≈ 0. atol = Precision

        # MAT Test (carbon concentration atmosphere)
        True_MAT = getparams(f, "B87:BI87", :all, "Base", T);
        @test maximum(abs, m[:co2cycle, :MAT] .- True_MAT) ≈ 0. atol = Precision

        # DAMFRAC Test (damages fraction)
        True_DAMFRAC = getparams(f, "B105:BI105", :all, "Base", T);
        @test maximum(abs, m[:damages, :DAMFRAC] .- True_DAMFRAC) ≈ 0. atol = Precision

        # DAMAGES Test (damages $)
        True_DAMAGES = getparams(f, "B106:BI106", :all, "Base", T);
        @test maximum(abs, m[:damages, :DAMAGES] .- True_DAMAGES) ≈ 0. atol = Precision

        # E Test (emissions)
        True_E = getparams(f, "B112:BI112", :all, "Base", T);
        @test maximum(abs, m[:emissions, :E] .- True_E) ≈ 0. atol = Precision

        # YGROSS Test (gross output)
        True_YGROSS = getparams(f, "B104:BI104", :all, "Base", T);
        @test maximum(abs, m[:grosseconomy, :YGROSS] .- True_YGROSS) ≈ 0. atol = Precision

        # CPC Test (per capita consumption)
        True_CPC = getparams(f, "B126:BI126", :all, "Base", T);
        @test maximum(abs, m[:neteconomy, :CPC] .- True_CPC) ≈ 0. atol = Precision

        # FORC Test (radiative forcing)
        True_FORC = getparams(f, "B100:BI100", :all, "Base", T);
        @test maximum(abs, m[:radiativeforcing, :FORC] .- True_FORC) ≈ 0. atol = Precision

        True_UTILITY = getparams(f, "B129:B129", :single, "Base", T);
        @test maximum(abs, m[:welfare, :UTILITY] .- True_UTILITY) ≈ 0. atol = Precision

    end # MimiDICE2013-model testset

    # ------------------------------------------------------------------------------
    #   2. Run tests to make sure integration version (Mimi v0.5.0)
    #   values match Mimi 0.4.0 values
    # ------------------------------------------------------------------------------
    @testset "MimiDICE2013-integration" begin

        Precision = 1.0e-10
        nullvalue = -999.999

        m = MimiDICE2013.get_model();
        run(m)

        for c in map(nameof, Mimi.compdefs(m)), v in Mimi.variable_names(m, c)

            # load data for comparison
            filepath = joinpath(@__DIR__, "../data/validation_data_v040/$c-$v.csv")
            results = m[c, v]

            df = load(filepath) |> DataFrame
            if typeof(results) <: Number
                validation_results = df[1,1]
        
            else
                validation_results = Matrix(df)

                # remove NaNs and Missings
                results[ismissing.(results)] .= nullvalue
                results[isnan.(results)] .= nullvalue
                validation_results[ismissing.(validation_results)] .= nullvalue
                validation_results[isnan.(validation_results)] .= nullvalue

                # match dimensions
        
                if size(validation_results, 1) == 1
                    validation_results = validation_results'
                end

            end
            @test results ≈ validation_results atol = Precision

        end # for loop

    end # MimiDICE2013-integration testset

    # ------------------------------------------------------------------------------
    #   3. Standard API
    # ------------------------------------------------------------------------------
    @testset "Standard API" begin

        m = MimiDICE2013.get_model()
        run(m)

        # Test the errors
        @test_throws ErrorException MimiDICE2013.compute_scc()  # test that it errors if you don't specify a year
        @test_throws ErrorException MimiDICE2013.compute_scc(year=2021)  # test that it errors if the year isn't in the time index
        @test_throws ErrorException MimiDICE2013.compute_scc(last_year=2299)  # test that it errors if the last_year isn't in the time index
        @test_throws ErrorException MimiDICE2013.compute_scc(year=2105, last_year=2100)  # test that it errors if the year is after last_year

        # Test the SCC 
        scc1 = MimiDICE2013.compute_scc(year=2020)
        @test scc1 isa Float64

        # Test that it's smaller with a shorter horizon
        scc2 = MimiDICE2013.compute_scc(year=2020, last_year=2200)
        @test scc2 < scc1

        # Test that it's smaller with a larger prtp
        scc3 = MimiDICE2013.compute_scc(year=2020, last_year=2200, prtp=0.02)
        @test scc3 < scc2

        # Test with a modified model 
        m = MimiDICE2013.get_model()
        update_param!(m, :t2xco2, 5)    
        scc4 = MimiDICE2013.compute_scc(m, year=2020)
        @test scc4 > scc1   # Test that a higher value of climate sensitivty makes the SCC bigger
      
        # Test compute_scc_mm
        result = MimiDICE2013.compute_scc_mm(year=2030)
        @test result.scc isa Float64
        @test result.mm isa Mimi.MarginalModel
        marginal_temp = result.mm[:climatedynamics, :TATM]
        @test all(marginal_temp[1:findfirst(isequal(2030), MimiDICE2013.model_years)] .== 0.)
        @test all(marginal_temp[findfirst(isequal(2035), MimiDICE2013.model_years):end] .!= 0.)

    end

    # ------------------------------------------------------------------------------
    #   4. Deterministic SCC values
    # ------------------------------------------------------------------------------
    @testset "SCC values" begin

        atol = 1e-4 # TODO what is a reasonable tolerance given we test on a few different machines etc.

        # Test several validation configurations against the pre-saved values from MimiDICE2013
        specs = Dict([
            :year => [2020, 2050],
            :eta => [0, 1.5],
            :prtp => [0.015, 0.03],
            :last_year => [2200, 2305],
        ])
        
        results = DataFrame(year = [], eta = [], prtp = [], last_year = [], SC = [])
        
        for year in specs[:year]
            for eta in specs[:eta]
                for prtp in specs[:prtp]
                    for last_year in specs[:last_year]
                        sc = MimiDICE2013.compute_scc(year=Int(year), eta=eta, prtp=prtp, last_year=Int(last_year))
                        push!(results, (year, eta, prtp, last_year, sc))
                    end
                end
            end
        end
            
        validation_results = load(joinpath(@__DIR__, "..", "data", "SC validation data", "deterministic_sc_values_v1-0-2.csv")) |> DataFrame
        # diffs = sort(results[!, :SC] - validation_results[!, :SC], rev = true)
        # println(diffs)
        @test all(isapprox.(results[!, :SC], validation_results[!, :SC], atol = atol))

    end # SCC values testset
end # MimiDICE2013 testset

nothing
