"""Pytest configuration file for Preloop tests."""

from typing import Generator
import inspect

import pytest
import os
import fastapi
from dotenv import load_dotenv
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker

from unittest.mock import patch, MagicMock

from fastapi.testclient import TestClient

from preloop.api.app import create_app
from preloop.api.auth import get_current_active_user
from preloop.models.db.session import get_db_session as get_db
from preloop.models.models.user import User
from preloop.models.crud import crud_account, crud_user


async def maybe_await(result):
    """Await the result if it's a coroutine, otherwise return it directly.

    This helper allows tests to work with both sync and async endpoint functions.
    In EE builds, the RBAC decorator wraps sync functions in async wrappers.
    In OSS builds, sync functions remain sync.
    """
    if inspect.iscoroutine(result):
        return await result
    return result


@pytest.fixture(autouse=True)
def mock_openai_client():
    """Mock the OpenAI client to avoid real API calls."""
    with patch("openai.Client") as mock_client:
        mock_embedding = MagicMock()
        mock_embedding.embedding = [0.1] * 1536
        mock_response = MagicMock()
        mock_response.data = [mock_embedding]
        mock_client.return_value.embeddings.create.return_value = mock_response
        yield mock_client


def pytest_configure(config):
    """
    Load environment variables from .env file before tests run.
    """
    # Set TESTING mode to skip external service connections (NATS, MCP, etc.)
    os.environ["TESTING"] = "true"

    # Disable RBAC permission checks during unit tests
    # This ensures tests work consistently regardless of whether the EE RBAC
    # plugin is available (which wraps endpoints in async wrappers)
    os.environ["DISABLE_RBAC"] = "true"

    # Construct the path to the .env file relative to the conftest.py file
    # Assuming .env is in the project root, and conftest.py is in tests/
    dotenv_path = os.path.join(os.path.dirname(__file__), "..", ".env")
    load_dotenv(dotenv_path)


@pytest.fixture(scope="session")
def db_engine():
    """Create a new database engine for the test session.

    NOTE: This does NOT create tables. The database schema must already exist
    via Alembic migrations (run `alembic upgrade head` before tests).
    Tests use transaction rollbacks for isolation.
    """
    database_url = os.environ.get("DATABASE_URL")
    if not database_url:
        raise ValueError("DATABASE_URL environment variable not set")

    engine = create_engine(database_url)
    # NOTE: Tables are created via Alembic, not here!
    # This prevents accidental schema recreation during tests.
    yield engine


@pytest.fixture(scope="function")
def db_session(db_engine) -> Generator[Session, None, None]:
    """Create a database session for each test function, and clean up afterwards."""
    connection = db_engine.connect()
    transaction = connection.begin()
    session = sessionmaker(autocommit=False, autoflush=False, bind=connection)()

    # Explicitly flush to ensure roles are visible in this transaction
    session.flush()

    yield session

    session.close()
    transaction.rollback()
    connection.close()


@pytest.fixture(scope="function")
def test_user(db_session: Session) -> User:
    """Create and persist a test user for authentication with owner role."""
    from preloop.models.crud import crud_role, crud_user_role

    # Create account (organization) first
    account_data = {
        "organization_name": "Test Organization",
        "is_active": True,
    }
    account = crud_account.create(db_session, obj_in=account_data)

    # Create user linked to the account
    user_data = {
        "account_id": account.id,
        "email": "test@example.com",
        "username": "testuser",
        "full_name": "Test User",
        "is_active": True,
        "email_verified": True,
        "hashed_password": "testpassword",
        "user_source": "local",
    }
    user = crud_user.create(db_session, obj_in=user_data)
    db_session.flush()

    # Assign owner role to test user (has all permissions)
    owner_role = crud_role.get_by_name(db_session, name="owner")
    if owner_role:
        crud_user_role.create(
            db_session,
            obj_in={"user_id": user.id, "role_id": owner_role.id},
        )
        db_session.flush()

    db_session.refresh(user)
    return user


@pytest.fixture(scope="function")
def test_tracker(db_session: Session, test_user: User):
    """Create a test tracker for testing."""
    from preloop.models.crud import crud_tracker
    from preloop.models.schemas.tracker import TrackerCreate

    tracker_data = TrackerCreate(
        name="Test Tracker",
        tracker_type="github",
        url="https://github.com/test",
        api_key="test_key",
        account_id=test_user.account_id,
        is_active=True,
    )
    tracker = crud_tracker.create(db_session, obj_in=tracker_data.model_dump())
    db_session.flush()
    return tracker


@pytest.fixture(scope="function")
def test_viewer_user(db_session: Session) -> User:
    """Create a test user with viewer role (read-only permissions)."""
    from preloop.models.crud import crud_role, crud_user_role

    account_data = {
        "organization_name": "Test Organization",
        "is_active": True,
    }
    account = crud_account.create(db_session, obj_in=account_data)

    user_data = {
        "account_id": account.id,
        "email": "viewer@example.com",
        "username": "vieweruser",
        "full_name": "Viewer User",
        "is_active": True,
        "email_verified": True,
        "hashed_password": "testpassword",
        "user_source": "local",
    }
    user = crud_user.create(db_session, obj_in=user_data)
    db_session.flush()

    # Assign viewer role
    viewer_role = crud_role.get_by_name(db_session, name="viewer")
    if viewer_role:
        crud_user_role.create(
            db_session,
            obj_in={"user_id": user.id, "role_id": viewer_role.id},
        )
        db_session.flush()

    db_session.refresh(user)
    return user


@pytest.fixture(scope="function")
def test_editor_user(db_session: Session) -> User:
    """Create a test user with editor role (can create and edit)."""
    from preloop.models.crud import crud_role, crud_user_role

    account_data = {
        "organization_name": "Test Organization",
        "is_active": True,
    }
    account = crud_account.create(db_session, obj_in=account_data)

    user_data = {
        "account_id": account.id,
        "email": "editor@example.com",
        "username": "editoruser",
        "full_name": "Editor User",
        "is_active": True,
        "email_verified": True,
        "hashed_password": "testpassword",
        "user_source": "local",
    }
    user = crud_user.create(db_session, obj_in=user_data)
    db_session.flush()

    # Assign editor role
    editor_role = crud_role.get_by_name(db_session, name="editor")
    if editor_role:
        crud_user_role.create(
            db_session,
            obj_in={"user_id": user.id, "role_id": editor_role.id},
        )
        db_session.flush()

    db_session.refresh(user)
    return user


def assign_role_to_user(db_session: Session, user: User, role_name: str) -> None:
    """Helper function to assign a role to a user.

    Args:
        db_session: Database session
        user: User to assign role to
        role_name: Name of the role (e.g., 'owner', 'admin', 'editor', 'viewer')
    """
    from preloop.models.crud import crud_role, crud_user_role

    role = crud_role.get_by_name(db_session, name=role_name)
    if role:
        # Check if user already has this role
        existing = crud_user_role.get_by_user_and_role(
            db_session, user_id=user.id, role_id=role.id
        )
        if not existing:
            crud_user_role.create(
                db_session,
                obj_in={"user_id": user.id, "role_id": role.id},
            )
            db_session.flush()


@pytest.fixture(scope="function")
def app(db_session: Session, test_user: User) -> Generator[fastapi.FastAPI, None, None]:
    """Create a FastAPI app for testing with dependency overrides."""

    def override_get_current_active_user():
        return test_user

    app = create_app()
    app.dependency_overrides[get_db] = lambda: db_session
    app.dependency_overrides[get_current_active_user] = override_get_current_active_user
    yield app


@pytest.fixture(scope="function")
def client(app: fastapi.FastAPI) -> Generator[TestClient, None, None]:
    """Create a test client for the FastAPI app."""
    with TestClient(app) as client:
        yield client
