from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime
from typing import Any, Callable, Tuple

from sqlalchemy import Table

from sandwich.modeling import Dv2SystemInfo, modeling_metadata, StgInfo

@dataclass(frozen=True)
class ValidationResult:
    stg_schema: str
    entity_name: str
    bk_keys: list[Tuple[str, Any]]
    hk_keys: list[Tuple[str, Any]]
    business_column_types: dict[str, Any]
    system_column_types: dict[str, Any]
    template: str
    degenerate_field: Tuple[str, Any] | None = None

class Validator(ABC):
    @abstractmethod
    def validate_staging(self, stg_info: StgInfo, sys_info: Dv2SystemInfo, verbose: bool = False) -> ValidationResult:
        pass

class BaseValidator(Validator):
    def __init__(self, template: str):
        self._on_validate_staging: Callable[[StgInfo, Dv2SystemInfo], None] | None = None
        self.template = template

    def validate_staging(self, stg_info: StgInfo, sys_info: Dv2SystemInfo, verbose: bool = False) -> ValidationResult:
        """Validate staging table or view for `scd2dim` template.

        Raises: Exception"""
        if verbose:
            raise Exception("verbose is not implemented yet")

        system_column_names = stg_info.sys_columns.keys()

        # universal check - all dv2 raw objects should be auditable
        for required_col in modeling_metadata.required_columns:
            if required_col not in system_column_names:
                raise Exception(f"{required_col} column is required")

        if self._on_validate_staging is not None:
            self._on_validate_staging(stg_info, sys_info)

        # todo: ValidationResult is not required whatsoever
        return ValidationResult(
            stg_schema=stg_info.stg_schema,
            entity_name=stg_info.stg_name,
            bk_keys=[(nm, tp) for nm, tp in stg_info.bk_keys.items()],
            hk_keys=[(nm, tp) for nm, tp in stg_info.hk_keys.items()],
            degenerate_field = stg_info.degenerate_field,
            business_column_types=stg_info.bus_columns,
            system_column_types=stg_info.sys_columns,
            template=self.template
        )



class SchemaGenerator(ABC):
    @abstractmethod
    def make_tables(self) -> dict[str, Table]:
        pass

    @abstractmethod
    def make_procedures(
        self,
        tables: dict[str, Table],
        entity_registration_date: datetime = datetime.now()
    ) -> dict[str, Tuple[str, str, str]]:
        pass

# class BaseSchemaGenerator(SchemaGenerator):
#     def __init__(self, dialect_handler: DialectHandler, validation_result: ValidationResult):
#         self.dialect_handler = dialect_handler
#         self._validation_result = validation_result
#         self._on_make_proc: Callable[[Table, datetime], Tuple[str, str, str]] | None = None
#
#     def make_proc(self, tbl: Table, entity_registration_date: datetime) -> Tuple[str, str, str]:
#         header = modeling_metadata.HEADER_TEMPLATE.format(
#             created_on=entity_registration_date,
#             updated_on=datetime.now(),
#             version=SANDWICH_VERSION,
#             entity_name=self._validation_result.entity_name
#         )
#
#         if self._validation_result.stg_schema == "proxy":
#             stg_proc_code, stg_proc_name, stg_call_stmt = self.dialect_handler.make_stg_materialization_proc(
#                 entity_name=self._validation_result.entity_name,
#                 header=header
#             )
#             procedures["stg"] = (stg_proc_code, stg_proc_name, stg_call_stmt)
