"""
Module to check PSBoard is dispatchable.

Use [`interactive_dispatch_checker`](@ref) for interactive use in QAQC.
"""
module DispatchChecker

using SQLite
using DBInterface
using DataFrames
using Printf

export DbConnection
export is_dispatchable

"""
Stores connection to database.

    DbConnection(db::SQLite.DB)

Constructor.
"""
mutable struct DbConnection
    db::SQLite.DB
    df_single_result::DataFrame
    df_extra_results::DataFrame
    df_runs::DataFrame
    function DbConnection(db::SQLite.DB)
        df_single_results =
            DBInterface.execute(db, sql"select * from qaqc_single_run_results") |> DataFrame
        df_extra_results =
            DBInterface.execute(db, sql"select * from qaqc_extra_run_results") |> DataFrame
        df_runs = DBInterface.execute(db, sql"select * from qaqc_runs") |> DataFrame
        new(db, df_single_results, df_extra_results, df_runs)
    end
end

THRESHOLD_INSUFFICIENT_RESET_WITH_10_CAMPAIGN_1to5 = 0.1
THRESHOLD_INSUFFICIENT_RESET_WITH_10_CAMPAIGN_6 = 0.05
THRESHOLD_RESET_FAILED_THOUGH_RECONFIG_DONE = 0.1
THRESHOLD_ALWAYS_HIT_FLAG_TRUE = 0.1
THRESHOLD_BCID_FAIL = 0.05

"""
    is_dispatchable(conn::DbConnection, psbid::Int64)

Test whether the PS Board with `psbid` is dispatchable from QAQC results in `conn`.
`conn` is type of [`DbConnection`](@ref).
Since the current implemented logic is somewhat simple, it returns `missing` if it cannot be decided.
"""
function is_dispatchable(conn::DbConnection, psbid::Int64)
    single_results = filter(:psboard_id => ==(psbid), conn.df_single_result)
    extra_results = filter(:psboard_id => ==(psbid), conn.df_extra_results)

    is_single_passed::Bool =
        nrow(single_results) == 1 && let
            single_result = Tables.rowtable(single_results) |> first

            # Clock test update was wrong
            # manually assign to psboards whose clock test failed
            if 221 <= single_result.runid < 234
                if single_result.psboard_id == 915
                    false
                elseif single_result.psboard_id in [860, 889, 876, 892]
                    # though 860 has 2+ rows
                    true
                end
            else
                single_result.resistance_test_passed == 1 &&
                    single_result.qspip == 1 &&
                    single_result.recov == 1 &&
                    single_result.power == 1 &&
                    single_result.clock == 1
            end
        end
    @debug "" is_single_passed single_results single_results.note
    is_extra_passed::Bool =
        nrow(extra_results) == 1 && let
            extra_result = Tables.rowtable(extra_results) |> first
            f1 =
                !ismissing(extra_result.insufficient_reset_with_10) && begin
                    campaign_id =
                        filter(:id => ==(extra_result.runid), conn.df_runs).campaign_id
                    @assert length(campaign_id) == 1
                    campaign_id = first(campaign_id)
                    if campaign_id ≤ 5
                        extra_result.insufficient_reset_with_10 >=
                        extra_result.num_tests *
                        THRESHOLD_INSUFFICIENT_RESET_WITH_10_CAMPAIGN_1to5
                    else
                        extra_result.insufficient_reset_with_10 >=
                        extra_result.num_tests *
                        THRESHOLD_INSUFFICIENT_RESET_WITH_10_CAMPAIGN_6
                    end
                end
            f2 =
                !ismissing(extra_result.reset_failed_though_reconfig_done) &&
                extra_result.reset_failed_though_reconfig_done >=
                extra_result.num_tests * THRESHOLD_RESET_FAILED_THOUGH_RECONFIG_DONE
            f3 =
                !ismissing(extra_result.always_hit_flag_true) &&
                extra_result.always_hit_flag_true >=
                extra_result.num_tests * THRESHOLD_ALWAYS_HIT_FLAG_TRUE
            f4 =
                !ismissing(extra_result.bcid_fail) &&
                extra_result.bcid_fail >= extra_result.num_tests * THRESHOLD_BCID_FAIL
            @debug "" extra_result extra_result.note f1 f2 f3 f4
            !(f1 || f2 || f3 || f4)
        end
    @debug "" is_extra_passed extra_results

    if is_single_passed & is_extra_passed
        return true
    end

    # TODO: not yet implemented
    @info "results" sort(single_results, :runid) sort(
        select(
            extra_results,
            Not(
                :id,
                :num_tests,
                :dac_is_0,
                :bcid_fail_111,
                :bcid_fail_000,
                :low_efficiency,
            ),
        ),
        :runid,
    )
    @debug "results(full)" extra_results
    return missing
end

"""
Interactive session for QAQC to check PSBoard is ready for dispatch.
"""
function interactive_dispatch_checker end

"""
    interactive_dispatch_checker(conn::DbConnection)
"""
function interactive_dispatch_checker(conn::DbConnection)
    dispatch_list = Int64[]

    println("Type \"quit\" to exit")
    for _ in 1:1000
        printstyled("PSBoard ID: ", bold = true)
        psbid = let
            rawin = readline()
            if lowercase(rawin) == "quit"
                printstyled("Quit\n", italic = true)
                println()
                break
            end
            m = match(r"^PS(\d+)", rawin)
            if isnothing(m)
                printstyled("Invalid input\n", color = :red)
                continue
            end
            parse(Int64, m[1])
        end
        isdispatchable = is_dispatchable(conn, psbid)
        if ismissing(isdispatchable)
            printstyled("Please determine [y/n]: ", underline = true, color = :cyan)
            isdispatchable = let
                rawin = readline()
                @info "" rawin
                if rawin == "y" || rawin == "Y"
                    true
                elseif rawin == "n" || rawin == "N"
                    false
                else
                    @warn "Invalid input falling back to \"no\""
                    false
                end
            end
        end
        if isdispatchable
            printstyled("Ok\n", bold = true, color = :green)
            if psbid in dispatch_list
                println("PSBoard ID $(psbid) is already in dispatch list")
            else
                push!(dispatch_list, psbid)
                println("Added to dispatch list")
            end
        else
            printstyled("No\n", bold = true, color = :red)
        end
    end

    printstyled("Finished\n")

    map(dispatch_list) do psbid
        @sprintf "PS%06d" psbid
    end |> (v -> join(v, "\n")) |> print
    println()

    printstyled("Paste the result to google sheets\n", underline = true)
    @info "Tips: You can use `join(ans, \"\\n\") |> clipboard` in REPL to copy the result to the clipboard"

    return dispatch_list
end

"""
    interactive_dispatch_checker(database_file::AbstractString)

Interactive session for QAQC to check provided PSBoard is ready to dispatch.
"""
function interactive_dispatch_checker(database_file::AbstractString)
    conn = DbConnection(SQLite.DB(database_file))
    interactive_dispatch_checker(conn)
end

end # module DispatchChecker
