"""enter data in attribute tables

Revision ID: cceebfa64b83
Revises: e58cc402c887
Create Date: 2021-11-03 16:38:07.931890

"""
from alembic import op

from sqlalchemy.orm.session import Session

from sqlalchemy import MetaData
from flask_sqlalchemy import SQLAlchemy

# revision identifiers, used by Alembic.
revision = "cceebfa64b83"
down_revision = "e58cc402c887"
branch_labels = None
depends_on = None

#
convention = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    # "ck": "ck_%(table_name)s_%(constraint_name)s",
    "ck": "ck_%(table_name)s_%(column_0_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}
metadata = MetaData(naming_convention=convention)

sa = SQLAlchemy(metadata=metadata)

#
from sqlalchemy.orm import declared_attr
from sqlalchemy.orm import declarative_mixin
import enum


@declarative_mixin
class AttributeBase:
    iid = sa.Column(sa.Integer(), primary_key=True)

    @declared_attr
    def product_id(self):
        return sa.Column(
            sa.Integer(),
            sa.ForeignKey("products.product_id"),
            nullable=False,
        )

    @declared_attr
    def product(self):
        return sa.relationship(
            "Product",
            backref=sa.backref(
                self.backref_column,
                cascade="all, delete-orphan",
            ),
        )

    @declared_attr
    def type_field_association_iid(self):
        return sa.Column(
            sa.Integer(),
            sa.ForeignKey("type_field_association.iid"),
            nullable=True,
        )
        # TODO: Make nullable False

    @declared_attr
    def type_field_association(self):
        return sa.relationship(
            "TypeFieldAssociation",
            backref=sa.backref(
                self.backref_column,  # TODO: need to change
                cascade="all, delete-orphan",
            ),
        )

    @declared_attr
    def field_id(self):
        return sa.Column(
            sa.Integer(),
            sa.ForeignKey("field.field_id"),
            nullable=True,
        )
        # TODO: remove, replace with type_field_association_iid

    @declared_attr
    def field(self):
        return sa.relationship(
            "Field",
            backref=sa.backref(
                self.backref_column,
                cascade="all, delete-orphan",
            ),
        )
        # TODO: remove, replace with type_field_association

    def __repr__(self):
        return (
            f"<{self.__class__.__name__} {self.field_name!r}: {self.value!r}>"
        )

    @property
    def field_name(self):
        try:
            return self.field.name
        except BaseException:
            return self.field


class AttributeUnicodeText(AttributeBase, sa.Model):
    __tablename__ = "attribute_unicode_text"
    backref_column = "attributes_unicode_text"
    value = sa.Column(sa.UnicodeText())


class AttributeBoolean(AttributeBase, sa.Model):
    __tablename__ = "attribute_boolean"
    backref_column = "attributes_boolean"
    value = sa.Column(sa.Boolean())


class AttributeInteger(AttributeBase, sa.Model):
    __tablename__ = "attribute_integer"
    backref_column = "attributes_integer"
    value = sa.Column(sa.Integer())


class AttributeFloat(AttributeBase, sa.Model):
    __tablename__ = "attribute_float"
    backref_column = "attributes_float"
    value = sa.Column(sa.Float())


class AttributeDate(AttributeBase, sa.Model):
    __tablename__ = "attribute_date"
    backref_column = "attributes_date"
    value = sa.Column(sa.Date())


class AttributeDateTime(AttributeBase, sa.Model):
    __tablename__ = "attribute_date_time"
    backref_column = "attributes_date_time"
    value = sa.Column(sa.DateTime())


class AttributeTime(AttributeBase, sa.Model):
    __tablename__ = "attribute_time"
    backref_column = "attributes_time"
    value = sa.Column(sa.Time())


class FieldType(enum.Enum):
    # SQLAlchemy generic types: https://docs.sqlalchemy.org/en/14/core/type_basics.html#generic-types
    UnicodeText = 1
    Boolean = 2
    Integer = 3
    Float = 4
    Date = 5
    DateTime = 6
    Time = 7

    @property
    def attribute_class(self):
        return FIELDTYPE_ATTRIBUTECLASS_MAP[self]


FIELDTYPE_ATTRIBUTECLASS_MAP = {
    FieldType.UnicodeText: AttributeUnicodeText,
    FieldType.Boolean: AttributeBoolean,
    FieldType.Integer: AttributeInteger,
    FieldType.Float: AttributeFloat,
    FieldType.Date: AttributeDate,
    FieldType.DateTime: AttributeDateTime,
    FieldType.Time: AttributeTime,
}


saEnumFieldType = sa.Enum(FieldType)  # to be imported in another module


class Field(sa.Model):
    __tablename__ = "field"
    field_id = sa.Column(sa.Integer(), primary_key=True)
    name = sa.Column(sa.UnicodeText(), nullable=False)
    type_ = sa.Column(saEnumFieldType, nullable=False)

    def __repr__(self):
        return f"<{self.__class__.__name__} {self.name!r} {self.type_name!r}>"

    @property
    def type_name(self):
        # used in __repr__()
        try:
            return self.type_.name
        except BaseException:
            return self.type_


class TypeFieldAssociation(sa.Model):
    __tablename__ = "type_field_association"
    iid = sa.Column(sa.Integer(), primary_key=True)
    type_id = sa.Column(
        sa.Integer(),
        sa.ForeignKey("product_types.type_id"),
    )
    field_id = sa.Column(
        sa.Integer(),
        sa.ForeignKey("field.field_id"),
        nullable=False,
    )
    type_ = sa.relationship(
        "ProductType",
        backref=sa.backref("fields", cascade="all, delete-orphan"),
    )
    field = sa.relationship(
        "Field",
        backref=sa.backref("entry_types"),
    )

    __table_args__ = (
        sa.UniqueConstraint("type_id", "field_id", name="_type_field"),
    )

    def __repr__(self):
        return f"<{self.__class__.__name__} {self.type_name!r} {self.field_name!r}>"

    @property
    def type_name(self):
        # used in __repr__()
        try:
            return self.type_.name
        except BaseException:
            return self.type_

    @property
    def field_name(self):
        # used in __repr__()
        try:
            return self.field.name
        except BaseException:
            return self.field


class ProductType(sa.Model):
    __tablename__ = "product_types"
    type_id = sa.Column(sa.Integer(), primary_key=True)
    name = sa.Column(sa.Text(), nullable=False, unique=True, index=True)
    order = sa.Column(sa.Integer())
    indef_article = sa.Column(sa.Text())
    singular = sa.Column(sa.Text())
    plural = sa.Column(sa.Text())
    icon = sa.Column(sa.Text())

    def __repr__(self):
        return f"<{self.__class__.__name__} {self.name!r}>"


class Product(sa.Model):
    __tablename__ = "products"
    product_id = sa.Column(sa.Integer(), primary_key=True)
    type_id = sa.Column(sa.ForeignKey("product_types.type_id"), nullable=False)
    type_ = sa.relationship("ProductType", backref=sa.backref("products"))
    name = sa.Column(sa.Text(), nullable=False)
    contact = sa.Column(sa.Text())
    date_produced = sa.Column(sa.Date())
    produced_by = sa.Column(sa.Text())
    time_posted = sa.Column(
        sa.DateTime(), default=lambda: datetime.datetime.now()
    )
    posted_by = sa.Column(sa.Text())
    posting_git_hub_user_id = sa.Column(sa.ForeignKey("github_users.user_id"))
    posting_git_hub_user = sa.relationship(
        "GitHubUser",
        foreign_keys=[posting_git_hub_user_id],
        backref=sa.backref("posted_products", cascade="all"),
    )
    time_updated = sa.Column(sa.DateTime())
    updated_by = sa.Column(sa.Text())
    updating_git_hub_user_id = sa.Column(sa.ForeignKey("github_users.user_id"))
    updating_git_hub_user = sa.relationship(
        "GitHubUser",
        foreign_keys=[updating_git_hub_user_id],
        backref=sa.backref("updated_products", cascade="all"),
    )
    note = sa.Column(sa.Text())
    __table_args__ = (
        sa.UniqueConstraint("type_id", "name", name="_type_id_name"),
    )

    def __repr__(self):
        return f"<{self.__class__.__name__} {self.name!r}>"


class GitHubUser(sa.Model):
    __tablename__ = "github_users"
    user_id = sa.Column(sa.Integer(), primary_key=True)
    git_hub_id = sa.Column(sa.Text(), unique=True, nullable=False)
    login = sa.Column(sa.Text(), unique=True, nullable=False)
    name = sa.Column(sa.Text())
    avatar_url = sa.Column(sa.Text())
    url = sa.Column(sa.Text())

    def __repr__(self):
        return f"<{self.__class__.__name__} {self.login!r}>"


#
def upgrade():
    session = Session(bind=op.get_bind())

    with session.no_autoflush:
        for field_type in FieldType.__members__.values():
            for attr in session.query(field_type.attribute_class):
                assoc = session.query(TypeFieldAssociation).filter_by(
                    type_=attr.product.type_, field=attr.field
                ).one()
                attr.type_field_association = assoc
    session.commit()


def downgrade():
    pass
