"""Test configuration and fixtures for PunimTag backend tests."""

from __future__ import annotations

import os
from typing import Generator

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

# Prevent DeepFace/TensorFlow from loading during tests (causes illegal instruction on some CPUs)
# Set environment variable BEFORE any backend imports that might trigger DeepFace/TensorFlow
os.environ["SKIP_DEEPFACE_IN_TESTS"] = "1"

from backend.app import create_app
from backend.db.base import Base
from backend.db.session import get_db

# Test database URL - use environment variable or default
TEST_DATABASE_URL = os.getenv(
    "DATABASE_URL",
    "postgresql+psycopg2://postgres:postgres@localhost:5432/punimtag_test"
)


@pytest.fixture(scope="session")
def test_db_engine():
    """Create test database engine and initialize schema."""
    engine = create_engine(TEST_DATABASE_URL, future=True)
    
    # Create all tables
    Base.metadata.create_all(bind=engine)
    
    yield engine
    
    # Cleanup: drop all tables after tests
    Base.metadata.drop_all(bind=engine)
    engine.dispose()


@pytest.fixture(scope="function")
def test_db_session(test_db_engine) -> Generator[Session, None, None]:
    """Create a test database session with transaction rollback.
    
    Each test gets a fresh session that rolls back after the test completes.
    """
    connection = test_db_engine.connect()
    transaction = connection.begin()
    session = sessionmaker(bind=connection, autoflush=False, autocommit=False)()
    
    yield session
    
    # Rollback transaction and close connection
    session.close()
    transaction.rollback()
    connection.close()


@pytest.fixture(scope="function")
def test_client(test_db_session: Session) -> Generator[TestClient, None, None]:
    """Create a test client with test database dependency override."""
    app = create_app()
    
    def override_get_db() -> Generator[Session, None, None]:
        yield test_db_session
    
    app.dependency_overrides[get_db] = override_get_db
    
    with TestClient(app) as client:
        yield client
    
    # Clear dependency overrides after test
    app.dependency_overrides.clear()


@pytest.fixture
def admin_user(test_db_session: Session):
    """Create an admin user for testing."""
    from backend.db.models import User
    from backend.utils.password import hash_password
    from backend.constants.roles import DEFAULT_ADMIN_ROLE
    
    user = User(
        username="testadmin",
        email="testadmin@example.com",
        password_hash=hash_password("testpass"),
        full_name="Test Admin",
        is_admin=True,
        is_active=True,
        role=DEFAULT_ADMIN_ROLE,
    )
    test_db_session.add(user)
    test_db_session.commit()
    test_db_session.refresh(user)
    return user


@pytest.fixture
def regular_user(test_db_session: Session):
    """Create a regular user for testing."""
    from backend.db.models import User
    from backend.utils.password import hash_password
    from backend.constants.roles import DEFAULT_USER_ROLE
    
    user = User(
        username="testuser",
        email="testuser@example.com",
        password_hash=hash_password("testpass"),
        full_name="Test User",
        is_admin=False,
        is_active=True,
        role=DEFAULT_USER_ROLE,
    )
    test_db_session.add(user)
    test_db_session.commit()
    test_db_session.refresh(user)
    return user


@pytest.fixture
def inactive_user(test_db_session: Session):
    """Create an inactive user for testing."""
    from backend.db.models import User
    from backend.utils.password import hash_password
    from backend.constants.roles import DEFAULT_USER_ROLE
    
    user = User(
        username="inactiveuser",
        email="inactiveuser@example.com",
        password_hash=hash_password("testpass"),
        full_name="Inactive User",
        is_admin=False,
        is_active=False,
        role=DEFAULT_USER_ROLE,
    )
    test_db_session.add(user)
    test_db_session.commit()
    test_db_session.refresh(user)
    return user


@pytest.fixture
def auth_token(test_client: TestClient, admin_user) -> str:
    """Get authentication token for admin user."""
    response = test_client.post(
        "/api/v1/auth/login",
        json={"username": "testadmin", "password": "testpass"}
    )
    assert response.status_code == 200
    return response.json()["access_token"]


@pytest.fixture
def regular_auth_token(test_client: TestClient, regular_user) -> str:
    """Get authentication token for regular user."""
    response = test_client.post(
        "/api/v1/auth/login",
        json={"username": "testuser", "password": "testpass"}
    )
    assert response.status_code == 200
    return response.json()["access_token"]


@pytest.fixture
def auth_headers(auth_token: str) -> dict[str, str]:
    """Get authentication headers for admin user."""
    return {"Authorization": f"Bearer {auth_token}"}


@pytest.fixture
def regular_auth_headers(regular_auth_token: str) -> dict[str, str]:
    """Get authentication headers for regular user."""
    return {"Authorization": f"Bearer {regular_auth_token}"}


@pytest.fixture
def test_photo(test_db_session: Session):
    """Create a test photo."""
    from backend.db.models import Photo
    from datetime import date
    
    photo = Photo(
        path="/test/path/photo1.jpg",
        filename="photo1.jpg",
        date_taken=date(2024, 1, 15),
        processed=True,
        media_type="image",
    )
    test_db_session.add(photo)
    test_db_session.commit()
    test_db_session.refresh(photo)
    return photo


@pytest.fixture
def test_photo_2(test_db_session: Session):
    """Create a second test photo."""
    from backend.db.models import Photo
    from datetime import date
    
    photo = Photo(
        path="/test/path/photo2.jpg",
        filename="photo2.jpg",
        date_taken=date(2024, 1, 16),
        processed=True,
        media_type="image",
    )
    test_db_session.add(photo)
    test_db_session.commit()
    test_db_session.refresh(photo)
    return photo


@pytest.fixture
def test_face(test_db_session: Session, test_photo):
    """Create a test face (unidentified)."""
    from backend.db.models import Face
    import numpy as np
    
    # Create a dummy encoding (128-dimensional vector like DeepFace)
    encoding = np.random.rand(128).astype(np.float32).tobytes()
    
    face = Face(
        photo_id=test_photo.id,
        person_id=None,  # Unidentified
        encoding=encoding,
        location='{"x": 100, "y": 100, "w": 200, "h": 200}',
        quality_score=0.85,
        face_confidence=0.95,
        detector_backend="retinaface",
        model_name="VGG-Face",
        pose_mode="frontal",
        excluded=False,
    )
    test_db_session.add(face)
    test_db_session.commit()
    test_db_session.refresh(face)
    return face


@pytest.fixture
def test_face_2(test_db_session: Session, test_photo_2):
    """Create a second test face (unidentified)."""
    from backend.db.models import Face
    import numpy as np
    
    # Create a similar encoding (for similarity testing)
    encoding = np.random.rand(128).astype(np.float32).tobytes()
    
    face = Face(
        photo_id=test_photo_2.id,
        person_id=None,  # Unidentified
        encoding=encoding,
        location='{"x": 150, "y": 150, "w": 200, "h": 200}',
        quality_score=0.80,
        face_confidence=0.90,
        detector_backend="retinaface",
        model_name="VGG-Face",
        pose_mode="frontal",
        excluded=False,
    )
    test_db_session.add(face)
    test_db_session.commit()
    test_db_session.refresh(face)
    return face


@pytest.fixture
def test_person(test_db_session: Session):
    """Create a test person."""
    from backend.db.models import Person
    from datetime import date, datetime
    
    person = Person(
        first_name="John",
        last_name="Doe",
        middle_name="Middle",
        maiden_name=None,
        date_of_birth=date(1990, 1, 1),
        created_date=datetime.utcnow(),
    )
    test_db_session.add(person)
    test_db_session.commit()
    test_db_session.refresh(person)
    return person


@pytest.fixture
def identified_face(test_db_session: Session, test_photo, test_person):
    """Create an identified face (already linked to a person)."""
    from backend.db.models import Face, PersonEncoding
    import numpy as np
    
    # Create encoding
    encoding = np.random.rand(128).astype(np.float32).tobytes()
    
    face = Face(
        photo_id=test_photo.id,
        person_id=test_person.id,
        encoding=encoding,
        location='{"x": 200, "y": 200, "w": 200, "h": 200}',
        quality_score=0.90,
        face_confidence=0.98,
        detector_backend="retinaface",
        model_name="VGG-Face",
        pose_mode="frontal",
        excluded=False,
    )
    test_db_session.add(face)
    test_db_session.flush()
    
    # Create person encoding
    person_encoding = PersonEncoding(
        person_id=test_person.id,
        face_id=face.id,
        encoding=encoding,
        quality_score=0.90,
        detector_backend="retinaface",
        model_name="VGG-Face",
    )
    test_db_session.add(person_encoding)
    test_db_session.commit()
    test_db_session.refresh(face)
    return face

