"""Async SQLAlchemy engine for initiative PostgreSQL persistence."""

from __future__ import annotations

import logging
import os
from contextlib import asynccontextmanager
from typing import AsyncGenerator, Optional

from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine

logger = logging.getLogger(__name__)

_engine: Optional[AsyncEngine] = None
_session_factory: Optional[async_sessionmaker[AsyncSession]] = None

# Inline from migrations/014_registration_otp.sql — applied on first engine init if missing
# (avoids relying on subprocess migrate script; idempotent).
_DDL_REGISTRATION_OTP_TABLE = """
CREATE TABLE IF NOT EXISTS registration_otp_codes (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  otp_hash TEXT NOT NULL,
  expires_at TIMESTAMPTZ NOT NULL,
  failed_attempts INT NOT NULL DEFAULT 0,
  used_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
)
"""
_DDL_REGISTRATION_OTP_INDEX = """
CREATE INDEX IF NOT EXISTS idx_registration_otp_codes_user_pending
  ON registration_otp_codes (user_id)
  WHERE used_at IS NULL
"""


async def _ensure_registration_otp_table(conn: AsyncConnection) -> None:
    row = (
        await conn.execute(
            text(
                """
                SELECT 1
                FROM information_schema.tables
                WHERE table_schema = 'public'
                  AND table_name = 'registration_otp_codes'
                LIMIT 1
                """
            )
        )
    ).first()
    if row is not None:
        return
    logger.info("initiative_db: creating registration_otp_codes (migration 014 equivalent)")
    await conn.execute(text(_DDL_REGISTRATION_OTP_TABLE))
    await conn.execute(text(_DDL_REGISTRATION_OTP_INDEX))
    await conn.execute(
        text(
            "COMMENT ON TABLE registration_otp_codes IS "
            "'Hashed 6-digit OTP for register verification; pending rows deleted when superseded by resend.'"
        )
    )


def get_database_url() -> Optional[str]:
    return os.getenv("INITIATIVE_DATABASE_URL") or os.getenv("DATABASE_URL")


def is_postgres_enabled() -> bool:
    url = get_database_url()
    return bool(url and url.startswith("postgresql"))


async def init_engine() -> None:
    """Create async engine and session factory. No-op if URL missing."""
    global _engine, _session_factory
    if not is_postgres_enabled():
        return
    if _engine is not None:
        return
    url = get_database_url()
    assert url is not None
    _engine = create_async_engine(url, echo=os.getenv("SQL_ECHO", "").lower() in ("1", "true", "yes"))
    _session_factory = async_sessionmaker(_engine, expire_on_commit=False)
    async with _engine.begin() as conn:
        await conn.execute(text("SELECT 1"))
        await _ensure_registration_otp_table(conn)


async def dispose_engine() -> None:
    global _engine, _session_factory
    if _engine is not None:
        await _engine.dispose()
    _engine = None
    _session_factory = None


def get_session_factory() -> async_sessionmaker[AsyncSession]:
    if _session_factory is None:
        raise RuntimeError("Initiative DB not initialized; call init_engine() on startup.")
    return _session_factory


@asynccontextmanager
async def get_session() -> AsyncGenerator[AsyncSession, None]:
    if is_postgres_enabled() and _session_factory is None:
        await init_engine()
    factory = get_session_factory()
    async with factory() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise
