import json
from typing import Any, Final, override

from pgvector.sqlalchemy import Vector
from sqlalchemy import (
    Boolean,
    Column,
    DateTime,
    Enum,
    ForeignKey,
    Index,
    Integer,
    MetaData,
    Numeric,
    String,
    Table,
    TypeDecorator,
    UniqueConstraint,
    text,
)
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import registry

from template_project.application.access_token.entity import AccessToken
from template_project.application.auth_identity.entity import AuthIdentity, AuthMethod
from template_project.application.common.enums import EducationGrade, ExperienceType
from template_project.application.notification_device.entity import NotificationDevice
from template_project.application.resume.entity import (
    Resume,
    ResumeEducation,
    ResumeEmbedding,
    ResumeExperience,
    ResumePrediction,
    ResumeProject,
)
from template_project.application.user.entity import User
from template_project.application.user.profile.entity import Profile
from template_project.application.vacancy.entity import Vacancy, VacancyEmbedding

meta_data: Final = MetaData()
mapper_registry: Final = registry()


class StringArrayType(TypeDecorator[list[str]]):
    impl: Any = JSONB
    cache_ok: bool | None = True

    @override
    def process_bind_param(self, value: Any, dialect: Any) -> Any:
        if value is None:
            return []
        if isinstance(value, list):
            return value
        return []

    @override
    def process_result_value(self, value: Any, dialect: Any) -> list[str]:
        if value is None:
            return []
        if isinstance(value, list):
            return [str(item) for item in value]
        if isinstance(value, str):
            try:
                parsed = json.loads(value)
                if isinstance(parsed, list):
                    return [str(item) for item in parsed]
            except (json.JSONDecodeError, TypeError):
                pass
        if isinstance(value, dict):
            return []
        return []


class ExperienceTypeType(TypeDecorator[ExperienceType]):
    impl: Any = String
    cache_ok: bool | None = True

    @override
    def process_bind_param(self, value: Any, dialect: Any) -> Any:
        if value is None:
            return None
        if isinstance(value, ExperienceType):
            return value.value
        if isinstance(value, str):
            return value
        return None

    @override
    def process_result_value(self, value: Any, dialect: Any) -> ExperienceType:
        if value is None:
            raise ValueError("experience_type cannot be None")
        if isinstance(value, ExperienceType):
            return value
        if isinstance(value, str):
            try:
                return ExperienceType(value)
            except ValueError as err:
                raise ValueError(f"Invalid experience_type value: {value}") from err
        raise ValueError(f"Cannot convert {type(value)} to ExperienceType")


user_table: Final = Table(
    "users",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
)

access_token_table: Final = Table(
    "access_token",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("user_id", UUID, ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
    Column("revoked", Boolean, nullable=False),
    Column("expires_in", DateTime(timezone=True), nullable=False),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
)

auth_identity_table: Final = Table(
    "auth_identities",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("user_id", UUID, ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
    Column("method", Enum(AuthMethod, name="auth_method"), nullable=False),
    Column("identifier", String, nullable=False),
    Column("secret_key", String, nullable=True),
    Column("created_at", DateTime(timezone=True), nullable=False),
    UniqueConstraint("method", "identifier", name="uq_auth_method_identifier"),
)

profile_table: Final = Table(
    "profiles",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("user_id", UUID, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True),
    Column("email", String, nullable=True),
    Column("display_name", String, nullable=True),
    Column("first_name", String, nullable=True),
    Column("last_name", String, nullable=True),
    Column("avatar_url", String, nullable=True),
    Column("phone", String, nullable=True),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
)

notification_device_table: Final = Table(
    "notification_devices",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("user_id", UUID, ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
    Column("device_id", String, nullable=False),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
    UniqueConstraint("user_id", "device_id", name="uq_user_device"),
)

resume_table: Final = Table(
    "resume",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
    Column("user_id", UUID, ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
    Column("position", String, nullable=False),
    Column("location", String, nullable=False),
    Column("about_me", String, nullable=False),
    Column("key_skills", StringArrayType(), nullable=False, server_default=text("'[]'::jsonb")),
    Column("experience_type", ExperienceTypeType(), nullable=False),
    Column("down_resume_id", UUID, ForeignKey("resume.id", ondelete="CASCADE"), nullable=True, default=None),
    Column("up_resume_id", UUID, ForeignKey("resume.id", ondelete="CASCADE"), nullable=True, default=None),
)

resume_embedding_table: Final = Table(
    "resume_embedding",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
    Column("resume_id", UUID, ForeignKey("resume.id", ondelete="CASCADE"), nullable=False),
    Column("vector", Vector(384), nullable=False),
)
resume_prediction_table: Final = Table(
    "resume_prediction",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
    Column("resume_id", UUID, ForeignKey("resume.id", ondelete="CASCADE"), nullable=False),
    Column("from_salary", Numeric, nullable=False),
    Column("to_salary", Numeric, nullable=False),
    Column("recommended_skills", StringArrayType(), nullable=False, server_default=text("'[]'::jsonb")),
)
key_skills_table: Final = Table(
    "key_skills",
    meta_data,
    Column("id", Integer, autoincrement=True, primary_key=True),
    Column("name", String, nullable=False, unique=True),
)

resume_experience_table: Final = Table(
    "resume_experience",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
    Column("resume_id", UUID, ForeignKey("resume.id", ondelete="CASCADE"), nullable=False),
    Column("place", String, nullable=False),
    Column("description", String, nullable=False),
    Column("months_duration", Integer, nullable=False),
)

resume_education_table: Final = Table(
    "resume_education",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
    Column("resume_id", UUID, ForeignKey("resume.id", ondelete="CASCADE"), nullable=False),
    Column("place", String, nullable=False),
    Column("grade", Enum(EducationGrade, name="education_grade"), nullable=False),
    Column("specialization", String, nullable=False),
    Column("description", String, nullable=True),
)

resume_project_table: Final = Table(
    "resume_project",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
    Column("resume_id", UUID, ForeignKey("resume.id", ondelete="CASCADE"), nullable=False),
    Column("name", String, nullable=False),
    Column("description", String, nullable=False),
)

vacancy_table: Final = Table(
    "vacancy",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
    Column("position", String, nullable=False),
    Column("from_salary", Numeric, nullable=False),
    Column("to_salary", Numeric, nullable=False),
    Column("experience_type", String, nullable=False),
    Column("description", String, nullable=False),
    Column("key_skills", StringArrayType(), nullable=False, server_default=text("'[]'::jsonb")),
)

vacancy_embedding_table: Final = Table(
    "vacancy_embedding",
    meta_data,
    Column("id", UUID, primary_key=True),
    Column("deleted_at", DateTime(timezone=True)),
    Column("created_at", DateTime(timezone=True), nullable=False),
    Column("vacancy_id", UUID, ForeignKey("vacancy.id", ondelete="CASCADE"), nullable=False),
    Column("vector", Vector(384), nullable=False),
)

key_skills_name_trgm_index: Final = Index(
    "ix_key_skills_name_trgm",
    key_skills_table.c.name,
    postgresql_using="gin",
    postgresql_ops={"name": "gin_trgm_ops"},
)

vacancy_embedding_vector_cosine_index: Final = Index(
    "ix_vacancy_embedding_vector_cosine",
    vacancy_embedding_table.c.vector,
    postgresql_using="hnsw",
    postgresql_ops={"vector": "vector_cosine_ops"},
    postgresql_with={"m": 32, "ef_construction": 256},
)


mapper_registry.map_imperatively(User, user_table)
mapper_registry.map_imperatively(AccessToken, access_token_table)
mapper_registry.map_imperatively(AuthIdentity, auth_identity_table)
mapper_registry.map_imperatively(Profile, profile_table)
mapper_registry.map_imperatively(NotificationDevice, notification_device_table)
mapper_registry.map_imperatively(
    Resume,
    resume_table,
    properties={
        "key_skills": resume_table.c.key_skills,
        "experience_type": resume_table.c.experience_type,
    },
)
mapper_registry.map_imperatively(ResumeEmbedding, resume_embedding_table)
mapper_registry.map_imperatively(
    ResumePrediction,
    resume_prediction_table,
    properties={
        "recommended_skills": resume_prediction_table.c.recommended_skills,
    },
)
mapper_registry.map_imperatively(ResumeExperience, resume_experience_table)
mapper_registry.map_imperatively(ResumeEducation, resume_education_table)
mapper_registry.map_imperatively(ResumeProject, resume_project_table)
mapper_registry.map_imperatively(
    Vacancy,
    vacancy_table,
    properties={
        "key_skills": vacancy_table.c.key_skills,
    },
)
mapper_registry.map_imperatively(VacancyEmbedding, vacancy_embedding_table)
