import logging
import re
from datetime import datetime
from pprint import pprint
from typing import List, Union

from dictalchemy.utils import fromdict
from pydantic import BaseModel
from sqlalchemy.orm.exc import MultipleResultsFound
from sqlalchemy.orm.session import make_transient

from opennem.core.loader import load_data
from opennem.db import SessionLocal, engine
from opennem.db.initdb import init_opennem
from opennem.db.load_fixtures import load_fixtures
from opennem.db.models.opennem import Facility, Location, Revision, Station
from opennem.importer.aemo_gi import gi_import
from opennem.importer.aemo_rel import rel_import
from opennem.importer.mms import mms_import
from opennem.importer.registry import registry_import
from opennem.schema.opennem import StationSchema

logger = logging.getLogger(__name__)
s = SessionLocal()


def load_revisions(stations):
    pass


def create_revision(model):
    # s = session()

    # make_transient(model)

    clone = model.asdict(exclude_pk=True)
    # clone.pop("id")
    # model.fromdict(clone)

    # pylint: disable=no-member
    clone_model = Station().fromdict(clone)

    return clone_model


def get_schema_name(record: object) -> str:
    class_name = str(record.__class__.__name__)

    class_name = re.sub("Schema", "", class_name)

    class_name = class_name.lower()

    return class_name


def revision_factory(
    record: BaseModel, field_names: Union[str, List[str]], created_by: str,
) -> bool:

    if isinstance(field_names, str):
        field_names = [field_names]

    revision_data = {}

    for field_name in field_names:
        field_type = get_schema_name(record)
        field_value = getattr(record, field_name)

        if isinstance(field_value, BaseModel):
            _value_dict = field_value.dict()

            if "id" in _value_dict:
                field_value = _value_dict["id"]

            elif "code" in _value_dict:
                field_value = _value_dict["code"]

            else:
                logger.error("Could not serialize data value %s", field_value)
                field_value = False

        if field_value is not False:
            revision_data[field_name] = field_value

    if not record.code:
        logger.error("Require a code to create a revision: %s", record)
        return False

    if len(revision_data.keys()) < 1:
        logger.error("Require data to lookup for code %s", record.code)
        return False

    __query = (
        s.query(Revision)
        .filter(Revision.schema == field_type)
        .filter(Revision.code == record.code)
    )

    for data_name, data_value in revision_data.items():
        if isinstance(data_value, str):
            __query = __query.filter(
                Revision.data[data_name].as_string() == data_value
            )
        if isinstance(data_value, bool):
            __query = __query.filter(
                Revision.data[data_name].as_boolean() == data_value
            )
        if isinstance(data_value, int):
            __query = __query.filter(
                Revision.data[data_name].as_integer() == data_value
            )
        if isinstance(data_value, float):
            __query = __query.filter(
                Revision.data[data_name].as_float() == data_value
            )

    revision_lookup = None

    try:
        revision_lookup = __query.one_or_none()
    except MultipleResultsFound:
        logger.info(
            "Revision exists: %s %s %s", record.code, field_name, field_value,
        )
        return False

    if revision_lookup:
        return False

    revision = Revision(
        schema=field_type,
        code=record.code,
        created_by=created_by,
        data=revision_data,
    )

    s.add(revision)
    s.commit()

    return True


def load_revision(records, created_by):
    logger.info("Running db test")

    for station_record in records:
        station_model = (
            s.query(Station)
            .filter(Station.code == station_record.code)
            .one_or_none()
        )

        if not station_model:
            logger.info(
                f"New station {station_record.name} {station_record.code}"
            )

            revision_factory(
                station_record, ["code", "name", "network_name"], created_by,
            )

        else:
            for field in ["name", "network_name"]:
                if getattr(station_model, field) != getattr(
                    station_record, field
                ):
                    revision_factory(station_record, field, created_by)

        for facility in station_record.facilities:
            facility_model = (
                s.query(Facility)
                .filter(Facility.code == facility.code)
                .first()
            )

            if not facility_model:
                logger.info(
                    "New facility %s => %s", station_record.name, facility.code
                )

                revision_factory(
                    facility, ["code", "dispatch_type"], created_by
                )

            for field in [
                "fueltech",
                "status",
                "network_region",
                "capacity_registered",
            ]:
                if hasattr(station_model, field) and getattr(
                    station_model, field
                ) != getattr(station_record, field):
                    revision_factory(facility, field, created_by)


def db_test():
    s.query(Revision).delete()

    mms = mms_import()
    rel = rel_import()
    gi = gi_import()

    load_revision(mms, "aemo.mms.202006")
    load_revision(rel, "aemo.rel.2020006")
    load_revision(gi, "aemo.gi.202006")


def registry_init():
    registry = registry_import()

    for station in registry:
        station_dict = station.dict(exclude={"id"})
        # pprint(station_dict)

        # pylint:disable no-member
        station_model = fromdict(Station(), station_dict)
        station_model.approved = True
        station_model.approved_at = datetime.now()
        station_model.approved_by = "opennem.registry"
        station_model.created_by = "opennem.registry"

        # location
        station_model.location = fromdict(Location(), station_dict["location"])

        if station.location.lat and station.location.lng:
            station_model.location.geom = "SRID=4326;POINT({} {})".format(
                station.location.lng, station.location.lat
            )

        for fac in station.facilities:
            f = Facility(
                **fac.dict(exclude={"id", "fueltech", "status", "network"})
            )

            f.network_id = fac.network.code

            if fac.fueltech:
                f.fueltech_id = fac.fueltech.code

            f.status_id = fac.status.code

            station_model.facilities.append(f)

        s.add(station_model)

    s.commit()


def test_revisions():
    registry = registry_import()
    # mms = mms_import()

    k = registry.get_code("KWINANA")

    station = Station(code=k.code, name=k.name, network_name=k.network_name,)
    s.add(station)
    s.commit()

    station_clone = station.asdict()
    station_clone.pop("id")

    pprint(station_clone)

    station = create_revision(station)

    station.location = Location(
        address1=k.location.address1,
        address2=k.location.address2,
        locality=k.location.locality,
        state=k.location.state,
        postcode=k.location.postcode,
    )

    s.add(station)
    s.commit()

    station = create_revision(station)

    station.name = "Kwinana Edited"

    s.add(station)
    s.commit()


def init():
    init_opennem(engine)
    logger.info("Db initialized")

    load_fixtures()
    logger.info("Fixtures loaded")

    registry_init()
    logger.info("Registry initialized")


if __name__ == "__main__":
    init()
    db_test()
