import os
import threading
import time
import traceback
from datetime import datetime as Datetime

from dateutil.relativedelta import relativedelta

from flask import g, request

import pytz

from sqlalchemy import Float, cast, func, or_
from sqlalchemy.orm import joinedload
from sqlalchemy.sql.expression import null, true

import chellow.computer
import chellow.dloads
from chellow.models import Bill, Channel, Era, HhDatum, Session, Site, SiteEra, Supply
from chellow.utils import HH, hh_format, hh_max, hh_min, req_int
from chellow.views import chellow_redirect


def _content(sess, tmp_file, year, month, months, supply_id, user):
    supplies = (
        sess.query(Supply)
        .join(Era)
        .distinct()
        .options(joinedload(Supply.generator_type))
    )

    if supply_id is not None:
        supply = Supply.get_by_id(sess, supply_id)
        supplies = supplies.filter(Supply.id == supply.id)

    caches = {}

    start_date = Datetime(year, month, 1, tzinfo=pytz.utc) - relativedelta(
        months=months - 1
    )

    field_names = (
        "supply-name",
        "source-code",
        "generator-type",
        "month",
        "pc-code",
        "msn",
        "site-code",
        "site-name",
        "metering-type",
        "import-mpan-core",
        "metered-import-kwh",
        "metered-import-net-gbp",
        "metered-import-estimated-kwh",
        "billed-import-kwh",
        "billed-import-net-gbp",
        "export-mpan-core",
        "metered-export-kwh",
        "metered-export-estimated-kwh",
        "billed-export-kwh",
        "billed-export-net-gbp",
        "problem",
        "timestamp",
    )

    tmp_file.write("supply-id," + ",".join(field_names) + "\n")

    forecast_date = chellow.computer.forecast_date()

    for i in range(months):
        month_start = start_date + relativedelta(months=i)
        month_finish = month_start + relativedelta(months=1) - HH

        for supply in supplies.filter(
            Era.start_date <= month_finish,
            or_(Era.finish_date == null(), Era.finish_date >= month_start),
        ):
            generator_type = supply.generator_type
            if generator_type is None:
                generator_type = ""
            else:
                generator_type = generator_type.code

            source_code = supply.source.code
            eras = supply.find_eras(sess, month_start, month_finish)
            era = eras[-1]
            metering_type = era.meter_category

            site = (
                sess.query(Site)
                .join(SiteEra)
                .filter(SiteEra.era == era, SiteEra.is_physical == true())
                .one()
            )

            values = {
                "supply-name": supply.name,
                "source-code": source_code,
                "generator-type": generator_type,
                "month": hh_format(month_finish),
                "pc-code": era.pc.code,
                "msn": era.msn,
                "site-code": site.code,
                "site-name": site.name,
                "metering-type": metering_type,
                "problem": "",
            }

            tmp_file.write(str(supply.id) + ",")

            for is_import, pol_name in [(True, "import"), (False, "export")]:
                if is_import:
                    mpan_core = era.imp_mpan_core
                else:
                    mpan_core = era.exp_mpan_core

                values[pol_name + "-mpan-core"] = mpan_core
                kwh = 0
                est_kwh = 0

                if metering_type in ["hh", "amr"]:
                    est_kwh = (
                        sess.query(HhDatum.value)
                        .join(Channel)
                        .join(Era)
                        .filter(
                            HhDatum.status == "E",
                            Era.supply_id == supply.id,
                            Channel.channel_type == "ACTIVE",
                            Channel.imp_related == is_import,
                            HhDatum.start_date >= month_start,
                            HhDatum.start_date <= month_finish,
                        )
                        .first()
                    )
                    if est_kwh is None:
                        est_kwh = 0
                    else:
                        est_kwh = est_kwh[0]

                if not (is_import and source_code in ("net", "gen-net")):
                    kwh_sum = (
                        sess.query(cast(func.sum(HhDatum.value), Float))
                        .join(Channel)
                        .join(Era)
                        .filter(
                            Era.supply_id == supply.id,
                            Channel.channel_type == "ACTIVE",
                            Channel.imp_related == is_import,
                            HhDatum.start_date >= month_start,
                            HhDatum.start_date <= month_finish,
                        )
                        .one()[0]
                    )
                    if kwh_sum is not None:
                        kwh += kwh_sum

                values["metered-" + pol_name + "-estimated-kwh"] = est_kwh
                values["metered-" + pol_name + "-kwh"] = kwh
                values["metered-" + pol_name + "-net-gbp"] = 0
                values["billed-" + pol_name + "-kwh"] = 0
                values["billed-" + pol_name + "-net-gbp"] = 0
                values["billed-" + pol_name + "-apportioned-kwh"] = 0
                values["billed-" + pol_name + "-apportioned-net-gbp"] = 0
                values["billed-" + pol_name + "-raw-kwh"] = 0
                values["billed-" + pol_name + "-raw-net-gbp"] = 0

            for bill in sess.query(Bill).filter(
                Bill.supply == supply,
                Bill.start_date <= month_finish,
                Bill.finish_date >= month_start,
            ):
                bill_start = bill.start_date
                bill_finish = bill.finish_date
                bill_duration = (bill_finish - bill_start).total_seconds() + 30 * 60
                overlap_duration = (
                    min(bill_finish, month_finish) - max(bill_start, month_start)
                ).total_seconds() + 30 * 60
                overlap_proportion = float(overlap_duration) / float(bill_duration)
                values["billed-import-net-gbp"] += overlap_proportion * float(bill.net)
                values["billed-import-kwh"] += overlap_proportion * float(bill.kwh)

            for era in eras:
                chunk_start = hh_max(era.start_date, month_start)
                chunk_finish = hh_min(era.finish_date, month_finish)

                import_mpan_core = era.imp_mpan_core
                if import_mpan_core is None:
                    continue

                supplier_contract = era.imp_supplier_contract

                if source_code in ["net", "gen-net", "3rd-party"]:
                    supply_source = chellow.computer.SupplySource(
                        sess,
                        chunk_start,
                        chunk_finish,
                        forecast_date,
                        era,
                        True,
                        caches,
                    )

                    values["metered-import-kwh"] += sum(
                        datum["msp-kwh"] for datum in supply_source.hh_data
                    )

                    import_vb_function = supply_source.contract_func(
                        supplier_contract, "virtual_bill"
                    )
                    if import_vb_function is None:
                        values["problem"] += (
                            "Can't find the "
                            "virtual_bill  function in the supplier "
                            "contract. "
                        )
                    else:
                        import_vb_function(supply_source)
                        values["metered-import-net-gbp"] += supply_source.supplier_bill[
                            "net-gbp"
                        ]

                    supply_source.contract_func(era.dc_contract, "virtual_bill")(
                        supply_source
                    )
                    values["metered-import-net-gbp"] += supply_source.dc_bill["net-gbp"]

                    mop_func = supply_source.contract_func(
                        era.mop_contract, "virtual_bill"
                    )
                    if mop_func is None:
                        values["problem"] += (
                            " MOP virtual_bill " "function can't be found."
                        )
                    else:
                        mop_func(supply_source)
                        mop_bill = supply_source.mop_bill
                        values["metered-import-net-gbp"] += mop_bill["net-gbp"]
                        if len(mop_bill["problem"]) > 0:
                            values["problem"] += (
                                " MOP virtual bill problem: " + mop_bill["problem"]
                            )

            values["timestamp"] = int(time.time() * 1000)
            tmp_file.write(
                ",".join('"' + str(values[name]) + '"' for name in field_names) + "\n"
            )


def content(year, month, months, supply_id, user):
    tmp_file = None
    try:
        with Session() as sess:
            if supply_id is None:
                base_name = (
                    f"supplies_monthly_duration_for_all_supplies_for_{months}_to_"
                    f"{year}_{month}.csv"
                )
            else:
                base_name = (
                    f"supplies_monthly_duration_for_{supply_id}_{months}_to_{year}_"
                    f"{month}.csv"
                )
            running_name, finished_name = chellow.dloads.make_names(base_name, user)

            tmp_file = open(running_name, "w")
            _content(sess, tmp_file, year, month, months, supply_id, user)
    except BaseException:
        tmp_file.write(traceback.format_exc())
    finally:
        tmp_file.close()
        os.rename(running_name, finished_name)


def do_get(sess):
    year = req_int("end_year")
    month = req_int("end_month")
    months = req_int("months")
    supply_id = req_int("supply_id") if "supply_id" in request.values else None
    user = g.user
    threading.Thread(
        target=content, args=(year, month, months, supply_id, user)
    ).start()
    return chellow_redirect("/downloads", 303)
