from abc import abstractmethod
from datetime import datetime
from typing import Callable, Tuple

from sqlalchemy import Table

from sandwich import SANDWICH_VERSION
from sandwich.dialects import DialectHandler
from sandwich.modeling.dataclasses import ValidationResult
from sandwich.modeling.metadata import modeling_metadata

from .schema_generator import SchemaGenerator


class BaseSchemaGenerator(SchemaGenerator):
    """ Generates `job` and `drop` procedures\n
    Does not make any tables

    """
    def __init__(self, dialect_handler: DialectHandler, validation_result: ValidationResult, entity_registration_date: datetime):
        self.dialect_handler = dialect_handler
        self._validation_result = validation_result
        self.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
        )
        self._on_make_procedures: list[Callable[[dict[str, Tuple[str, str, str]], dict[str, Table]], None]] = []

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

    def make_procedures(self, tables: dict[str, Table]) -> dict[str, Tuple[str, str, str]]:
        procedures: dict[str, Tuple[str, str, str]] = {}

        # extension point
        for on_make_proc in self._on_make_procedures:
            on_make_proc(procedures, tables)

        proc_names = [tup[1] for tup in procedures.values()]
        table_schemas = list(procedures.keys())

        # job procedure
        job_proc_code, job_proc_name, job_call_stmt = self.dialect_handler.make_job_proc(
            entity_name=self._validation_result.entity_name,
            proc_names=proc_names,
            header=self.header
        )
        procedures["job"] = (job_proc_code, job_proc_name, job_call_stmt)
        proc_names.append(job_proc_name)

        # drop procedure
        drop_proc_code, drop_proc_name, drop_call_stmt = self.dialect_handler.make_drop_proc(
            entity_name=self._validation_result.entity_name,
            table_schemas=table_schemas,
            procedures=proc_names,
            header=self.header
        )
        procedures["drop"] = (drop_proc_code, drop_proc_name, drop_call_stmt)

        return procedures
