import traceback
from collections import defaultdict

from sqlalchemy import null, or_, select
from sqlalchemy.orm import joinedload

from chellow.dloads import put_mem_val
from chellow.e.computer import (
    SiteSource,
    SupplySource,
    contract_func,
    displaced_era,
    forecast_date,
)
from chellow.models import Era, Session, Site, SiteEra, Source, Supply
from chellow.utils import c_months_u, ct_datetime_now, hh_format, hh_max, hh_min

CATS = ("imp_grid", "exp_grid", "used", "displaced")
QUANTS = (
    "kwh",
    "net_gbp",
    #    "p_per_kwh",
    "triad_gbp",
    "duos_gbp",
    "red_duos_gbp",
    "md_kw",
)


def _process(sess, log, start_year, start_month, mem_id, jval, site):
    log("started thread")
    f_date = forecast_date()
    report_context = {}

    for month_num, (month_start, month_finish) in enumerate(
        c_months_u(start_year=start_year, start_month=start_month, months=12)
    ):
        jval["progress"] = month_num
        put_mem_val(mem_id, jval)
        vals = {cat: defaultdict(int) for cat in CATS}
        vals["problem"] = ""

        for era in sess.execute(
            select(Era)
            .join(SiteEra)
            .join(Supply)
            .join(Source)
            .where(
                SiteEra.site == site,
                Era.start_date <= month_finish,
                or_(Era.finish_date == null(), Era.finish_date >= month_start),
                Era.imp_mpan_core != null(),
                Source.code.in_(("grid", "gen-grid")),
            )
            .options(
                joinedload(Era.imp_supplier_contract),
                joinedload(Era.exp_supplier_contract),
                joinedload(Era.mop_contract),
                joinedload(Era.dc_contract),
            )
        ).scalars():
            chunk_start = hh_max(era.start_date, month_start)
            chunk_finish = hh_min(era.finish_date, month_finish)

            log("about to call first supplysource")
            supply_ds = SupplySource(
                sess, chunk_start, chunk_finish, f_date, era, True, report_context
            )

            supplier_contract = era.imp_supplier_contract
            import_vb_function = contract_func(
                report_context, supplier_contract, "virtual_bill"
            )
            if import_vb_function is None:
                raise Exception(
                    "Can't find the import_virtual_bill function in the "
                    "supplier contract. "
                )
            else:
                import_vb_function(supply_ds)
                v_bill = supply_ds.supplier_bill

                if "problem" in v_bill and len(v_bill["problem"]) > 0:
                    raise Exception("Supplier Problem: " + v_bill["problem"])

                try:
                    vals["imp_grid"]["net_gbp"] += v_bill["net-gbp"]
                    if "triad-gbp" in v_bill:
                        vals["imp_grid"]["triad_gbp"] += v_bill["triad-gbp"]
                    if "duos-red-gbp" in v_bill:
                        vals["imp_grid"]["red_duos_gbp"] += v_bill["duos-red-gbp"]
                    vals["imp_grid"]["duos_gbp"] += sum(
                        v
                        for k, v in v_bill.items()
                        if k.endswith("-gbp") and k.startswith("duos-")
                    )
                except KeyError:
                    raise Exception(
                        f"For the era {era.id} the virtual bill {v_bill} "
                        f"from the contract {supplier_contract} does not "
                        f"contain the net-gbp key."
                    )

            dc_contract = era.dc_contract
            supply_ds.contract_func(dc_contract, "virtual_bill")(supply_ds)
            dc_bill = supply_ds.dc_bill
            vals["imp_grid"]["gbp"] += dc_bill["net-gbp"]
            if "problem" in dc_bill and len(dc_bill["problem"]) > 0:
                raise Exception(f'DC Problem: {dc_bill["problem"]}')

            mop_contract = era.mop_contract
            mop_bill_function = supply_ds.contract_func(mop_contract, "virtual_bill")
            mop_bill_function(supply_ds)
            mop_bill = supply_ds.mop_bill
            vals["imp_grid"]["gbp"] += mop_bill["net-gbp"]
            if "problem" in mop_bill and len(mop_bill["problem"]) > 0:
                raise Exception(f'MOP Problem: {mop_bill["problem"]}')

            exp_supplier_contract = era.exp_supplier_contract
            if exp_supplier_contract is not None:
                log("about to call second supply source")
                exp_supply_ds = SupplySource(
                    sess,
                    chunk_start,
                    chunk_finish,
                    f_date,
                    era,
                    False,
                    report_context,
                )
                exp_vb_function = contract_func(
                    report_context, exp_supplier_contract, "virtual_bill"
                )
                if exp_vb_function is None:
                    raise Exception(
                        "Can't find the export_virtual_bill function in the "
                        "supplier contract. "
                    )
                else:
                    exp_vb_function(exp_supply_ds)
                    v_bill = exp_supply_ds.supplier_bill

                    if "problem" in v_bill and len(v_bill["problem"]) > 0:
                        raise Exception(f"Export supplier Problem: {v_bill['problem']}")

                    try:
                        vals["exp_grid"]["net_gbp"] += v_bill["net-gbp"]
                        if "triad-gbp" in v_bill:
                            vals["exp_grid"]["triad_gbp"] += v_bill["triad-gbp"]
                        if "duos-red-gbp" in v_bill:
                            vals["exp_grid"]["red_duos_gbp"] += v_bill["duos-red-gbp"]
                        vals["exp_grid"]["duos_gbp"] += sum(
                            v
                            for k, v in v_bill.items()
                            if k.endswith("-gbp") and k.startswith("duos-")
                        )
                    except KeyError as e:
                        raise Exception(
                            f"For the era {era.id} the virtual bill {v_bill} from the "
                            f"contract {supplier_contract} does not contain the key {e}"
                        )

        disp_era = displaced_era(
            sess, report_context, site, month_start, month_finish, f_date
        )
        log("about to call sitesource")
        site_ds = SiteSource(
            sess,
            site,
            month_start,
            month_finish,
            f_date,
            report_context,
            disp_era,
            exclude_virtual=False,
        )

        if disp_era is not None:
            disp_supplier_contract = disp_era.imp_supplier_contract
            disp_vb_function = contract_func(
                report_context, disp_supplier_contract, "displaced_virtual_bill"
            )
            if disp_vb_function is None:
                raise Exception(
                    f"The supplier contract {disp_supplier_contract} "
                    "doesn't have the displaced_virtual_bill() function."
                )
            disp_vb_function(site_ds)
            disp_supplier_bill = site_ds.supplier_bill

            try:
                vals["displaced"]["net_gbp"] += disp_supplier_bill["net-gbp"]
            except KeyError:
                disp_supplier_bill["problem"] += (
                    f"For the supply {site_ds.mpan_core} the virtual bill "
                    f"{disp_supplier_bill} from the contract {disp_supplier_contract} "
                    f"does not contain the net-gbp key."
                )
            log(str(disp_supplier_bill))
            if "triad-gbp" in disp_supplier_bill:
                vals["displaced"]["triad_gbp"] += disp_supplier_bill["triad-gbp"]
            if "duos-red-gbp" in disp_supplier_bill:
                vals["displaced"]["red_duos_gbp"] += disp_supplier_bill["duos-red-gbp"]
            vals["displaced"]["duos_gbp"] += sum(
                v
                for k, v in disp_supplier_bill.items()
                if k.endswith("-gbp") and k.startswith("duos-")
            )

        for hh in site.hh_data(sess, month_start, month_finish, exclude_virtual=True):
            for cat in CATS:
                vals_cat = vals[cat]
                vals_cat["md_kw"] = max(vals_cat["md_kw"], hh[cat] * 2)
                vals_cat["kwh"] += float(hh[cat])

        for quant in ("net_gbp", "triad_gbp", "duos_gbp", "red_duos_gbp"):
            vals["used"][quant] += vals["imp_grid"][quant] + vals["displaced"][quant]

        """
        for cat in CATS:
            vals_cat = vals[cat]
            if vals_cat["kwh"] > 0:
                vals_cat["p_per_kwh"] = vals_cat["gbp"] / vals_cat["kwh"] * 100
            else:
                vals_cat["p_per_kwh"] = 0
        """

        FORMAT_LOOKUP = {
            "kwh": "{:,.0f}",
            "net_gbp": "{:,.2f}",
            "p_per_kwh": "{:,.2f}",
            "triad_gbp": "{:,.2f}",
            "duos_gbp": "{:,.2f}",
            "red_duos_gbp": "{:,.2f}",
            "md_kw": "{:,.2f}",
        }

        for quant in QUANTS:
            row = [hh_format(month_start)[:7], quant]
            fmt = FORMAT_LOOKUP[quant]
            for cat in CATS:
                row.append(fmt.format(vals[cat][quant]))
            jval["rows"].append(row)


def totals_runner(mem_id, site_id, start_date):
    sess = None
    titles = ["month", "quantity"]
    titles.extend(CATS)
    log_list = []

    jval = {"titles": titles, "rows": [], "status": "running", "log": log_list}
    try:
        with Session() as sess:
            site = Site.get_by_id(sess, site_id)

            def log(msg):
                return
                log_list.append(msg)
                put_mem_val(mem_id, jval)

            if start_date is None:
                now = ct_datetime_now()
                start_year = now.year - 1
                start_month = now.month
            else:
                start_year = start_date.year
                start_month = start_date.month

            _process(sess, log, start_year, start_month, mem_id, jval, site)

    except BaseException:
        msg = traceback.format_exc()
        jval["problem"] = msg
    finally:
        jval["status"] = "finished"
        put_mem_val(mem_id, jval)
