from datetime import datetime

from sqlalchemy import Connection, Engine, MetaData, select, Table, text, RowMapping, Sequence

from sandwich.dialects import DialectHandlerFactory
from sandwich.modeling import get_stg_info, infer_template, Dv2SystemInfo, Dv2Entity, StgInfo
from sandwich.modeling.strategies import SchemaGenerator, StrategyFactory


def _register_entity(entity_name: str, template: str, conn: Engine | Connection,
                         verbose: bool = False) -> datetime:
    entities = Table("entities", MetaData(), schema="core", autoload_with=conn)
    created_result = conn.execute(select(entities.c.created).where(entity_name == entities.c.entity_name)).scalar_one_or_none()

    if created_result is None:
        created_result = datetime.now()
        conn.execute(entities.insert().values(entity_name=entity_name, template=template, created=created_result))
        if verbose:
            print(f"[ok] Registered `{entity_name}` for `{template}`")
    else:
        _update_entity(entity_name, conn, entities, verbose=verbose)

    return created_result

def _update_entity(entity_name: str, conn: Engine | Connection, sys_entities: Table, verbose: bool = False) -> None:
    conn.execute(
        sys_entities.update().where(entity_name == sys_entities.c.entity_name).values(updated=datetime.now(), is_deleted=False))
    if verbose:
        print(f"[ok] Updated `{entity_name}`")


def generate_schema(schema_generator: SchemaGenerator, conn: Engine | Connection,
                    verbose: bool = False) -> None:
    tables = schema_generator.make_tables()
    for table_type, table in tables.items():
        if table is not None:
            table.create(conn, checkfirst=True)
            if verbose:
                print(f"[ok] Created table [{table.schema}].[{table.name}]")

    procedures = schema_generator.make_procedures(tables)
    for proc_type, (proc_code, proc_name, _) in procedures.items():
        conn.execute(text(proc_code))
        if verbose:
            print(f"[ok] Created or altered {proc_name}")


def _generate_schema_for_entity(stg_info: StgInfo, conn: Engine | Connection, dialect: str,
                                registered_on: datetime, template: str | None, verbose: bool = False) -> None:
    validator = StrategyFactory.create_validator(template)
    sys_info = get_system_info(conn)
    validation_result = validator.validate_staging(stg_info, sys_info)
    dialect_handler = DialectHandlerFactory.create_handler(dialect)
    schema_generator = StrategyFactory.create_generator(dialect_handler, validation_result, registered_on)
    generate_schema(schema_generator, conn, verbose=verbose)


def register_and_create_entity(entity_name: str, conn: Engine | Connection, dialect: str, template: str | None = None,
                               schema: str = "stg", verbose: bool = False) -> None:
    stg_info = get_stg_info(entity_name, schema, conn)
    if template is None:
        template = infer_template(stg_info)
    registered_on = _register_entity(entity_name, template, conn)
    _generate_schema_for_entity(stg_info, conn, dialect, registered_on, template, verbose=verbose)


def update_registered_entities(conn: Engine | Connection, dialect: str, schema: str = "stg",
                               verbose: bool = False) -> None:
    sys_info = get_system_info(conn)
    for en in sys_info.entities_list:
        stg_info = get_stg_info(en.entity_name, schema, conn)
        _update_entity(en.entity_name, conn, sys_info.sys_entities, verbose=verbose)
        _generate_schema_for_entity(stg_info, conn, dialect, en.created_on, en.template, verbose=verbose)


def get_system_info(conn: Engine | Connection):
    sys_entities = Table("entities", MetaData(), schema="core", autoload_with=conn)
    select_result = conn.execute(sys_entities.select().where(~sys_entities.c.is_deleted))
    return Dv2SystemInfo(
        [Dv2Entity(en["entity_name"], en["template"], en["created"]) for en in select_result.mappings().all()],
        sys_entities
    )
