from __future__ import annotations

import os
import subprocess
import sys
from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import inspect, text

from src.web.api.auth import router as auth_router
from src.web.api.faces import router as faces_router
from src.web.api.health import router as health_router
from src.web.api.jobs import router as jobs_router
from src.web.api.metrics import router as metrics_router
from src.web.api.people import router as people_router
from src.web.api.pending_identifications import router as pending_identifications_router
from src.web.api.photos import router as photos_router
from src.web.api.reported_photos import router as reported_photos_router
from src.web.api.tags import router as tags_router
from src.web.api.users import router as users_router
from src.web.api.version import router as version_router
from src.web.settings import APP_TITLE, APP_VERSION
from src.web.db.base import Base, engine
from src.web.db.session import database_url
# Import models to ensure they're registered with Base.metadata
from src.web.db import models  # noqa: F401
from src.web.utils.password import hash_password

# Global worker process (will be set in lifespan)
_worker_process: subprocess.Popen | None = None


def start_worker() -> None:
    """Start RQ worker in background subprocess."""
    global _worker_process

    try:
        from redis import Redis

        # Check Redis connection first
        redis_conn = Redis(host="localhost", port=6379, db=0, decode_responses=False)
        redis_conn.ping()

        # Start worker as a subprocess (avoids signal handler issues)
        project_root = Path(__file__).parent.parent.parent
        python_executable = sys.executable
        
        _worker_process = subprocess.Popen(
            [
                python_executable,
                "-m",
                "src.web.worker",
            ],
            cwd=str(project_root),
            stdout=None,  # Don't capture - let output go to console
            stderr=None,   # Don't capture - let errors go to console
            env={
                **{k: v for k, v in os.environ.items()},
                "PYTHONPATH": str(project_root),
            }
        )
        # Give it a moment to start, then check if it's still running
        import time
        time.sleep(0.5)
        if _worker_process.poll() is not None:
            # Process already exited - there was an error
            print(f"❌ Worker process exited immediately with code {_worker_process.returncode}")
            print("   Check worker errors above")
        else:
            print(f"✅ RQ worker started in background subprocess (PID: {_worker_process.pid})")
    except Exception as e:
        print(f"⚠️  Failed to start RQ worker: {e}")
        print("   Background jobs will not be processed. Ensure Redis is running.")


def stop_worker() -> None:
    """Stop RQ worker gracefully."""
    global _worker_process

    if _worker_process:
        try:
            _worker_process.terminate()
            try:
                _worker_process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                _worker_process.kill()
            print("✅ RQ worker stopped")
        except Exception:
            pass


def ensure_user_password_hash_column(inspector) -> None:
    """Ensure users table contains password_hash column."""
    if "users" not in inspector.get_table_names():
        print("ℹ️  Users table does not exist yet - will be created with password_hash column")
        return

    columns = {column["name"] for column in inspector.get_columns("users")}
    if "password_hash" in columns:
        print("ℹ️  password_hash column already exists in users table")
        return
    
    print("🔄 Adding password_hash column to users table...")

    default_hash = hash_password("changeme")
    dialect = engine.dialect.name

    with engine.connect() as connection:
        with connection.begin():
            if dialect == "postgresql":
                # PostgreSQL: Add column as nullable first, then update, then set NOT NULL
                connection.execute(
                    text("ALTER TABLE users ADD COLUMN IF NOT EXISTS password_hash TEXT")
                )
                connection.execute(
                    text(
                        "UPDATE users SET password_hash = :default_hash "
                        "WHERE password_hash IS NULL OR password_hash = ''"
                    ),
                    {"default_hash": default_hash},
                )
                # Set NOT NULL constraint
                connection.execute(
                    text("ALTER TABLE users ALTER COLUMN password_hash SET NOT NULL")
                )
            else:
                # SQLite
                connection.execute(
                    text("ALTER TABLE users ADD COLUMN password_hash TEXT")
                )
                connection.execute(
                    text(
                        "UPDATE users SET password_hash = :default_hash "
                        "WHERE password_hash IS NULL OR password_hash = ''"
                    ),
                    {"default_hash": default_hash},
                )
        print("✅ Added password_hash column to users table (default password: changeme)")


def ensure_user_password_change_required_column(inspector) -> None:
    """Ensure users table contains password_change_required column."""
    if "users" not in inspector.get_table_names():
        return

    columns = {column["name"] for column in inspector.get_columns("users")}
    if "password_change_required" in columns:
        print("ℹ️  password_change_required column already exists in users table")
        return
    
    print("🔄 Adding password_change_required column to users table...")
    dialect = engine.dialect.name

    with engine.connect() as connection:
        with connection.begin():
            if dialect == "postgresql":
                connection.execute(
                    text("ALTER TABLE users ADD COLUMN IF NOT EXISTS password_change_required BOOLEAN NOT NULL DEFAULT true")
                )
            else:
                # SQLite
                connection.execute(
                    text("ALTER TABLE users ADD COLUMN password_change_required BOOLEAN DEFAULT 1")
                )
                connection.execute(
                    text("UPDATE users SET password_change_required = 1 WHERE password_change_required IS NULL")
                )
        print("✅ Added password_change_required column to users table")


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Lifespan context manager for startup and shutdown events."""
    # Ensure database exists and tables are created on first run
    try:
        if database_url.startswith("sqlite"):
            db_path = database_url.replace("sqlite:///", "")
            db_file = Path(db_path)
            db_file.parent.mkdir(parents=True, exist_ok=True)
        
        # Only create tables if they don't already exist (safety check)
        inspector = inspect(engine)
        existing_tables = set(inspector.get_table_names())
        
        # Check if required application tables exist (not just alembic_version)
        required_tables = {"photos", "people", "faces", "tags", "phototaglinkage", "person_encodings", "photo_favorites", "users"}
        missing_tables = required_tables - existing_tables
        
        if missing_tables:
            # Some required tables are missing - create all tables
            # create_all() only creates missing tables, won't drop existing ones
            Base.metadata.create_all(bind=engine)
            if len(missing_tables) == len(required_tables):
                print("✅ Database initialized (first run - tables created)")
            else:
                print(f"✅ Database tables created (missing tables: {', '.join(missing_tables)})")
        else:
            # All required tables exist - don't recreate (prevents data loss)
            print(f"✅ Database already initialized ({len(existing_tables)} tables exist)")

        # Ensure new columns exist (backward compatibility without migrations)
        ensure_user_password_hash_column(inspector)
        ensure_user_password_change_required_column(inspector)
    except Exception as exc:
        print(f"❌ Database initialization failed: {exc}")
        raise
    # Startup
    start_worker()
    yield
    # Shutdown
    stop_worker()


def create_app() -> FastAPI:
    """Create and configure the FastAPI application instance."""
    app = FastAPI(
        title=APP_TITLE,
        version=APP_VERSION,
        lifespan=lifespan,
    )

    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    app.include_router(health_router, tags=["health"])
    app.include_router(version_router, tags=["meta"])
    app.include_router(metrics_router, tags=["metrics"])
    app.include_router(auth_router, prefix="/api/v1")
    app.include_router(jobs_router, prefix="/api/v1")
    app.include_router(photos_router, prefix="/api/v1")
    app.include_router(faces_router, prefix="/api/v1")
    app.include_router(people_router, prefix="/api/v1")
    app.include_router(pending_identifications_router, prefix="/api/v1")
    app.include_router(reported_photos_router, prefix="/api/v1")
    app.include_router(tags_router, prefix="/api/v1")
    app.include_router(users_router, prefix="/api/v1") 

    return app


app = create_app()


