"""SQLAlchemy ORM models for Sulci."""

import json
import logging

from sqlalchemy import Boolean, DateTime, Float, Integer, String, Text, func, text
from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

logger = logging.getLogger(__name__)


class Base(AsyncAttrs, DeclarativeBase):
    pass


class UserRow(Base):
    __tablename__ = "users"

    id: Mapped[str] = mapped_column(String(36), primary_key=True)
    email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
    hashed_password: Mapped[str | None] = mapped_column(Text, nullable=True)
    full_name: Mapped[str] = mapped_column(String(255), default="")
    tier: Mapped[str] = mapped_column(String(20), default="free")
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    google_id: Mapped[str | None] = mapped_column(String(255), unique=True, nullable=True)



class KnowledgeAtomRow(Base):
    __tablename__ = "knowledge_atoms"

    id: Mapped[str] = mapped_column(String(32), primary_key=True)
    atom_type: Mapped[str] = mapped_column(String(20), index=True)
    content: Mapped[str] = mapped_column(Text)
    subject: Mapped[str | None] = mapped_column(String(255), index=True)
    predicate: Mapped[str | None] = mapped_column(String(255))
    object: Mapped[str | None] = mapped_column(Text)
    confidence: Mapped[float] = mapped_column(Float, default=0.8)
    source_tool: Mapped[str | None] = mapped_column(String(50))
    tags_json: Mapped[str] = mapped_column(Text, default="[]")
    created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    last_accessed_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
    access_count: Mapped[int] = mapped_column(Integer, default=0)
    is_active: Mapped[bool] = mapped_column(Boolean, default=True)
    verified_at: Mapped[str | None] = mapped_column(DateTime(timezone=True), nullable=True, default=None)
    verification_status: Mapped[str] = mapped_column(String(20), default="unverified")
    projects_json: Mapped[str] = mapped_column(Text, default="[]")
    user_id: Mapped[str | None] = mapped_column(String(36), index=True, nullable=True)

    @property
    def tags(self) -> list[str]:
        return json.loads(self.tags_json)

    @tags.setter
    def tags(self, value: list[str]) -> None:
        self.tags_json = json.dumps(value)


class KnowledgeConflictRow(Base):
    __tablename__ = "knowledge_conflicts"

    id: Mapped[str] = mapped_column(String(32), primary_key=True)
    atom_a_id: Mapped[str] = mapped_column(String(32), index=True)
    atom_b_id: Mapped[str] = mapped_column(String(32), index=True)
    conflict_type: Mapped[str] = mapped_column(String(30))
    description: Mapped[str] = mapped_column(Text)
    status: Mapped[str] = mapped_column(String(20), default="open", index=True)
    resolved_winner_id: Mapped[str | None] = mapped_column(String(32), nullable=True)
    created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
    resolved_at: Mapped[str | None] = mapped_column(DateTime(timezone=True), nullable=True)
    user_id: Mapped[str | None] = mapped_column(String(36), index=True, nullable=True)


class InteractionRow(Base):
    __tablename__ = "interactions"

    id: Mapped[str] = mapped_column(String(32), primary_key=True)
    source_tool: Mapped[str] = mapped_column(String(50), index=True)
    messages_json: Mapped[str] = mapped_column(Text)
    extracted_atoms_json: Mapped[str] = mapped_column(Text, default="[]")
    created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
    metadata_json: Mapped[str] = mapped_column(Text, default="{}")
    user_id: Mapped[str | None] = mapped_column(String(36), index=True, nullable=True)


class ProjectRow(Base):
    __tablename__ = "projects"

    id: Mapped[str] = mapped_column(String(32), primary_key=True)
    name: Mapped[str] = mapped_column(String(255), unique=True, index=True)
    project_type: Mapped[str] = mapped_column(String(20), default="standard")
    data_mode: Mapped[str] = mapped_column(String(20), default="local")
    description: Mapped[str] = mapped_column(Text, default="")
    status: Mapped[str] = mapped_column(String(20), default="active")
    created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
    user_id: Mapped[str | None] = mapped_column(String(36), index=True, nullable=True)


class ContextInjectionRow(Base):
    __tablename__ = "context_injections"

    id: Mapped[str] = mapped_column(String(32), primary_key=True)
    target_tool: Mapped[str] = mapped_column(String(50), index=True)
    query_text: Mapped[str] = mapped_column(Text)
    atoms_injected_json: Mapped[str] = mapped_column(Text, default="[]")
    injection_text: Mapped[str] = mapped_column(Text)
    created_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
    user_id: Mapped[str | None] = mapped_column(String(36), index=True, nullable=True)


class UsageCounterRow(Base):
    __tablename__ = "usage_counters"

    user_id: Mapped[str] = mapped_column(String(36), primary_key=True, default="")
    counter_name: Mapped[str] = mapped_column(String(50), primary_key=True)
    period_key: Mapped[str] = mapped_column(String(20), primary_key=True)  # e.g. "2026-02", "2026-02-19"
    count: Mapped[int] = mapped_column(Integer, default=0)


class DismissedSuggestionRow(Base):
    __tablename__ = "dismissed_suggestions"

    name: Mapped[str] = mapped_column(String(255), primary_key=True)
    dismissed_at: Mapped[str] = mapped_column(DateTime(timezone=True), server_default=func.now())
    user_id: Mapped[str | None] = mapped_column(String(36), index=True, nullable=True)


async def _migrate_db(conn) -> None:
    """Add new columns to existing tables (safe for fresh databases too)."""
    migrations = [
        ("knowledge_atoms", "verified_at", "DATETIME DEFAULT NULL"),
        ("knowledge_atoms", "verification_status", "VARCHAR(20) DEFAULT 'unverified'"),
        ("knowledge_atoms", "projects_json", "TEXT DEFAULT '[]'"),
        # Multi-tenant user_id columns
        ("knowledge_atoms", "user_id", "VARCHAR(36) DEFAULT NULL"),
        ("knowledge_conflicts", "user_id", "VARCHAR(36) DEFAULT NULL"),
        ("interactions", "user_id", "VARCHAR(36) DEFAULT NULL"),
        ("projects", "user_id", "VARCHAR(36) DEFAULT NULL"),
        ("context_injections", "user_id", "VARCHAR(36) DEFAULT NULL"),
        ("dismissed_suggestions", "user_id", "VARCHAR(36) DEFAULT NULL"),
    ]
    for table, column, col_type in migrations:
        try:
            await conn.execute(text("SAVEPOINT _mig"))
            await conn.execute(text(f"ALTER TABLE {table} ADD COLUMN {column} {col_type}"))
            await conn.execute(text("RELEASE SAVEPOINT _mig"))
            logger.info("Added column %s.%s", table, column)
        except Exception:
            await conn.execute(text("ROLLBACK TO SAVEPOINT _mig"))
            logger.debug("Column %s.%s already exists (skipping migration)", table, column)

    # Auto-create project rows for existing inferred project names
    try:
        result = await conn.execute(text("SELECT projects_json FROM knowledge_atoms WHERE is_active = 1"))
        rows = result.fetchall()
        existing_names: set[str] = set()
        for row in rows:
            try:
                import json as _json

                proj_list = _json.loads(row[0] or "[]")
                existing_names.update(p for p in proj_list if p)
            except (ValueError, TypeError):
                logger.debug("Skipping malformed projects_json during migration")

        if existing_names:
            for name in existing_names:
                try:
                    from uuid import uuid4 as _uuid4

                    await conn.execute(
                        text(
                            "INSERT OR IGNORE INTO projects (id, name, project_type, data_mode, description, status) "
                            "VALUES (:id, :name, 'standard', 'local', '', 'active')"
                        ),
                        {"id": _uuid4().hex, "name": name},
                    )
                except Exception:
                    logger.debug("Project '%s' already exists or insert failed", name)
            logger.info("Auto-migrated %d inferred projects", len(existing_names))
    except Exception:
        logger.debug("Projects migration skipped (table may not exist yet)")

    # Migrate usage_counters to add user_id as part of PK
    # Strategy: rename old table, create new schema, copy data with user_id=""
    try:
        result = await conn.execute(text("SELECT name FROM sqlite_master WHERE type='table' AND name='usage_counters'"))
        if result.fetchone():
            # Check if user_id column already exists (migration already done)
            col_result = await conn.execute(text("PRAGMA table_info(usage_counters)"))
            cols = [row[1] for row in col_result.fetchall()]
            if "user_id" not in cols:
                await conn.execute(text("ALTER TABLE usage_counters RENAME TO usage_counters_old"))
                await conn.execute(
                    text(
                        "CREATE TABLE usage_counters ("
                        "  user_id VARCHAR(36) NOT NULL DEFAULT '',"
                        "  counter_name VARCHAR(50) NOT NULL,"
                        "  period_key VARCHAR(20) NOT NULL,"
                        "  count INTEGER NOT NULL DEFAULT 0,"
                        "  PRIMARY KEY (user_id, counter_name, period_key)"
                        ")"
                    )
                )
                await conn.execute(
                    text(
                        "INSERT INTO usage_counters (user_id, counter_name, period_key, count) "
                        "SELECT '', counter_name, period_key, count FROM usage_counters_old"
                    )
                )
                await conn.execute(text("DROP TABLE usage_counters_old"))
                logger.info("Migrated usage_counters table to include user_id in PK")
    except Exception as e:
        logger.debug("usage_counters migration skipped: %s", e)


async def init_db(db_url: str) -> async_sessionmaker:
    """Initialize the database and return a session factory.

    Dialect-aware: applies SQLite PRAGMAs and sqlite-vec extension loading
    only for SQLite URLs. PostgreSQL uses default QueuePool.
    """
    is_sqlite = db_url.startswith("sqlite")

    if is_sqlite:
        from sqlalchemy.pool import StaticPool

        engine = create_async_engine(
            db_url,
            echo=False,
            poolclass=StaticPool,  # Single connection for SQLite (appropriate for aiosqlite)
        )

        async with engine.begin() as conn:
            # SQLite performance and reliability pragmas
            await conn.execute(text("PRAGMA journal_mode=WAL"))
            await conn.execute(text("PRAGMA busy_timeout=5000"))
            await conn.execute(text("PRAGMA synchronous=NORMAL"))
            await conn.run_sync(Base.metadata.create_all)
            await _migrate_db(conn)
    else:
        # PostgreSQL — use default QueuePool with SSL required for hosted DBs
        engine = create_async_engine(
            db_url,
            echo=False,
            connect_args={"ssl": "require"},
        )
        # create_all and migrations run in SEPARATE transactions.
        # On Postgres a failed ALTER TABLE aborts the whole transaction, so if
        # they shared one, a migration failure would roll back create_all too.
        async with engine.begin() as conn:
            await conn.run_sync(Base.metadata.create_all)
        async with engine.begin() as conn:
            await _migrate_db(conn)

    return async_sessionmaker(engine, expire_on_commit=False)
