from __future__ import annotations

from typing import Generator

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine, text
from sqlalchemy.orm import Session, sessionmaker
from sqlalchemy.pool import StaticPool

from src.web.app import app
from src.web.db import models
from src.web.db.models import Photo, PhotoTagLinkage, Tag, User
from src.web.db.session import get_auth_db, get_db
from src.web.constants.roles import DEFAULT_ADMIN_ROLE
from src.web.api.auth import get_current_user


# Create isolated in-memory databases for main and auth stores.
main_engine = create_engine(
    "sqlite://",
    connect_args={"check_same_thread": False},
    poolclass=StaticPool,
)
auth_engine = create_engine(
    "sqlite://",
    connect_args={"check_same_thread": False},
    poolclass=StaticPool,
)

MainSessionLocal = sessionmaker(
    bind=main_engine, autoflush=False, autocommit=False, future=True
)
AuthSessionLocal = sessionmaker(
    bind=auth_engine, autoflush=False, autocommit=False, future=True
)

models.Base.metadata.create_all(bind=main_engine)

with auth_engine.begin() as connection:
    connection.execute(
        text(
            """
            CREATE TABLE users (
                id INTEGER PRIMARY KEY,
                name TEXT,
                email TEXT
            )
            """
        )
    )
    connection.execute(
        text(
            """
            CREATE TABLE pending_linkages (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                photo_id INTEGER NOT NULL,
                tag_id INTEGER,
                tag_name VARCHAR(255),
                user_id INTEGER NOT NULL,
                status VARCHAR(50) DEFAULT 'pending',
                notes TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
            """
        )
    )


def override_get_db() -> Generator[Session, None, None]:
    db = MainSessionLocal()
    try:
        yield db
    finally:
        db.close()


def override_get_auth_db() -> Generator[Session, None, None]:
    db = AuthSessionLocal()
    try:
        yield db
    finally:
        db.close()


def override_get_current_user() -> dict[str, str]:
    return {"username": "admin"}


app.dependency_overrides[get_db] = override_get_db
app.dependency_overrides[get_auth_db] = override_get_auth_db
app.dependency_overrides[get_current_user] = override_get_current_user

client = TestClient(app)


def _ensure_admin_user() -> None:
    with MainSessionLocal() as session:
        existing = session.query(User).filter(User.username == "admin").first()
        if existing:
            existing.is_admin = True
            existing.role = DEFAULT_ADMIN_ROLE
            session.commit()
            return

        admin_user = User(
            username="admin",
            password_hash="test",
            email="admin@example.com",
            full_name="Admin",
            is_active=True,
            is_admin=True,
            role=DEFAULT_ADMIN_ROLE,
        )
        session.add(admin_user)
        session.commit()


@pytest.fixture(autouse=True)
def clean_databases() -> Generator[None, None, None]:
    with MainSessionLocal() as session:
        session.query(PhotoTagLinkage).delete()
        session.query(Tag).delete()
        session.query(Photo).delete()
        session.query(User).filter(User.username != "admin").delete()
        session.commit()

    with AuthSessionLocal() as session:
        session.execute(text("DELETE FROM pending_linkages"))
        session.execute(text("DELETE FROM users"))
        session.commit()

    _ensure_admin_user()
    yield


def _insert_auth_user(user_id: int = 1) -> None:
    with auth_engine.begin() as connection:
        connection.execute(
            text(
                """
                INSERT INTO users (id, name, email)
                VALUES (:id, :name, :email)
                """
            ),
            {"id": user_id, "name": "Tester", "email": "tester@example.com"},
        )


def _insert_pending_linkage(
    photo_id: int,
    *,
    tag_id: int | None = None,
    tag_name: str | None = None,
    status: str = "pending",
    user_id: int = 1,
) -> int:
    with auth_engine.begin() as connection:
        result = connection.execute(
            text(
                """
                INSERT INTO pending_linkages (
                    photo_id, tag_id, tag_name, user_id, status, notes
                )
                VALUES (:photo_id, :tag_id, :tag_name, :user_id, :status, 'note')
                """
            ),
            {
                "photo_id": photo_id,
                "tag_id": tag_id,
                "tag_name": tag_name,
                "user_id": user_id,
                "status": status,
            },
        )
        return int(result.lastrowid)


def _create_photo(path: str, filename: str, file_hash: str) -> int:
    with MainSessionLocal() as session:
        photo = Photo(path=path, filename=filename, file_hash=file_hash)
        session.add(photo)
        session.commit()
        session.refresh(photo)
        return photo.id


def test_list_pending_linkages_returns_existing_rows():
    _ensure_admin_user()
    photo_id = _create_photo("/tmp/photo1.jpg", "photo1.jpg", "hash1")
    _insert_auth_user()
    linkage_id = _insert_pending_linkage(photo_id, tag_name="Beach Day")

    response = client.get("/api/v1/pending-linkages")
    assert response.status_code == 200

    payload = response.json()
    assert payload["total"] == 1
    item = payload["items"][0]
    assert item["photo_id"] == photo_id
    assert item["proposed_tag_name"] == "Beach Day"
    assert item["status"] == "pending"


def test_review_pending_linkages_creates_tag_and_linkage():
    _ensure_admin_user()
    photo_id = _create_photo("/tmp/photo2.jpg", "photo2.jpg", "hash2")
    _insert_auth_user()
    linkage_id = _insert_pending_linkage(photo_id, tag_name="Sunset Crew")

    response = client.post(
        "/api/v1/pending-linkages/review",
        json={"decisions": [{"id": linkage_id, "decision": "approve"}]},
    )
    assert response.status_code == 200

    payload = response.json()
    assert payload["approved"] == 1
    assert payload["denied"] == 0
    assert payload["tags_created"] == 1
    assert payload["linkages_created"] == 1

    with MainSessionLocal() as session:
        tags = session.query(Tag).all()
        assert len(tags) == 1
        assert tags[0].tag_name == "Sunset Crew"
        linkage = session.query(PhotoTagLinkage).first()
        assert linkage is not None
        assert linkage.photo_id == photo_id
        assert linkage.tag_id == tags[0].id

    with AuthSessionLocal() as session:
        statuses = session.execute(
            text("SELECT status FROM pending_linkages WHERE id = :id"),
            {"id": linkage_id},
        ).fetchone()
        assert statuses is not None
        assert statuses[0] == "approved"


def test_cleanup_pending_linkages_deletes_approved_and_denied():
    _ensure_admin_user()
    photo_id = _create_photo("/tmp/photo3.jpg", "photo3.jpg", "hash3")
    _insert_auth_user()
    
    approved_id = _insert_pending_linkage(photo_id, tag_name="Approved Tag", status="approved")
    denied_id = _insert_pending_linkage(photo_id, tag_name="Denied Tag", status="denied")
    pending_id = _insert_pending_linkage(photo_id, tag_name="Pending Tag", status="pending")
    
    response = client.post("/api/v1/pending-linkages/cleanup")
    assert response.status_code == 200
    
    payload = response.json()
    assert payload["deleted_records"] == 2
    
    with AuthSessionLocal() as session:
        remaining = session.execute(
            text("SELECT id, status FROM pending_linkages ORDER BY id")
        ).fetchall()
        assert len(remaining) == 1
        assert remaining[0][0] == pending_id
        assert remaining[0][1] == "pending"

