from enum import StrEnum
from typing import Optional, List, Dict

from fastapi_toolkit.generate.utils import plural, name_convert_to_snake
from fastapi_toolkit.define import ManyManyLink, Field

FIELD_NAME = str


class ModelMetadata:
    def __init__(self,
                 name: str,
                 fields: Dict[str, Field],
                 is_user: bool = False,
                 ):
        self.is_user: bool = is_user
        self.name: str = name
        self.plural_name: str = plural(name)
        self.snake_name: str = name_convert_to_snake(name)
        self.snake_plural_name: str = plural(self.snake_name)
        self.table_name: str = '__table_name_' + self.snake_name
        self.base_schema_name: str = name + 'Schema'

        self.fields: Dict[FIELD_NAME, Field] = {name: field for name, field in fields.items() if not field.primary_key}
        self.pk: Dict[FIELD_NAME, Field] = {name: field for name, field in fields.items() if field.primary_key}
        # { fk_name: pk in other table}
        self.fk: Dict[FIELD_NAME, FKMetadata] = {}

        self.relationship: List[RelationshipMetadata] = []
        self.relationship_combinations = []

    def require_one_pk(self):
        assert len(self.pk) == 1, f"model <{self.name}> must have only one pk"
        return list(self.pk.items())[0]


class LinkTableMetadata:
    def __init__(self, left: ModelMetadata, right: ModelMetadata,
                 link: ManyManyLink, data_model: Optional[ModelMetadata] = None):
        self.name = f'Association{left.name}With{right.name}'
        self.left = left
        self.right = right
        self.link = link
        self.table_name = f'association__{left.snake_name}__and__{right.snake_name}'
        self.left_pk_name, self.left_pk = left.require_one_pk()
        self.right_pk_name, self.right_pk = right.require_one_pk()
        self.data_model = data_model


class RelationshipSide(StrEnum):
    # one side of 1-n relationship
    one = 'one'
    # many side of 1-n relationship
    many = 'many'
    # side of n-n relationship
    both = 'both'


class RelationshipMetadata:
    def __init__(self,
                 target: ModelMetadata,
                 side: RelationshipSide,
                 link_table: Optional[LinkTableMetadata] = None,
                 ):
        self.target: ModelMetadata = target
        self.side: RelationshipSide = side
        self.link_table: Optional[LinkTableMetadata] = link_table
        self.link_fields: Dict[str, Field] = {}
        if side == RelationshipSide.both:
            if link_table.data_model is not None:
                self.link_fields.update(link_table.data_model.fields)


class FKMetadata:
    def __init__(self, field: Field, other_model: ModelMetadata):
        self.field = field
        self.other_model = other_model
