"""Unit tests for the onboarding wizard API (/api/onboarding).

Covers:
- GET /api/onboarding/status (auth required, new user, returning user)
- POST /api/onboarding/profile (saves display_name / contact_email)
- POST /api/onboarding/plan (saves tier + billing cycle, rejects invalid tiers)
- POST /api/onboarding/storage (saves preferred_destination)
- POST /api/onboarding/complete (marks onboarding_completed=True)
"""

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

from app.database import Base, get_db
from app.models import UserProfile

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

_TEST_USER = {
    "sub": "user-onb-123",
    "name": "Test User",
    "email": "test@example.com",
    "is_admin": False,
}


@pytest.fixture()
def ob_engine():
    """In-memory SQLite engine for onboarding tests."""
    engine = create_engine(
        "sqlite:///:memory:",
        connect_args={"check_same_thread": False},
        poolclass=StaticPool,
    )
    Base.metadata.create_all(bind=engine)
    yield engine
    Base.metadata.drop_all(bind=engine)


@pytest.fixture()
def ob_session(ob_engine):
    """DB session scoped to one test."""
    Session = sessionmaker(bind=ob_engine)
    session = Session()
    yield session
    session.close()


@pytest.fixture()
def ob_client_authed(ob_engine):
    """TestClient that bypasses auth by monkey-patching _get_current_user_id."""
    from app.api import onboarding as ob_module
    from app.main import app

    original = ob_module._get_current_user_id

    def override_db():
        Session = sessionmaker(bind=ob_engine)
        session = Session()
        try:
            yield session
        finally:
            session.close()

    def fake_user_id(_request):
        return _TEST_USER["sub"]

    ob_module._get_current_user_id = fake_user_id
    app.dependency_overrides[get_db] = override_db

    with TestClient(app, base_url="http://localhost", raise_server_exceptions=False) as client:
        yield client

    ob_module._get_current_user_id = original
    app.dependency_overrides.clear()


def _make_profile(session, user_id: str, **kwargs) -> UserProfile:
    """Insert a UserProfile row with sensible defaults."""
    kwargs.setdefault("is_blocked", False)
    kwargs.setdefault("onboarding_completed", False)
    profile = UserProfile(user_id=user_id, **kwargs)
    session.add(profile)
    session.commit()
    session.refresh(profile)
    return profile


# ---------------------------------------------------------------------------
# TestOnboardingAPI
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestOnboardingAPI:
    """Unit tests for the /api/onboarding endpoints."""

    # ------------------------------------------------------------------
    # GET /api/onboarding/status
    # ------------------------------------------------------------------

    def test_status_requires_auth(self, ob_engine):
        """Unauthenticated requests to /status must return 401."""
        from app.main import app

        def override_db():
            Session = sessionmaker(bind=ob_engine)
            session = Session()
            try:
                yield session
            finally:
                session.close()

        app.dependency_overrides[get_db] = override_db
        with TestClient(app, base_url="http://localhost", raise_server_exceptions=False) as client:
            # No session user injected → _get_current_user_id raises 401
            resp = client.get("/api/onboarding/status")
        app.dependency_overrides.clear()

        assert resp.status_code == 401

    def test_status_returns_not_completed_for_new_user(self, ob_client_authed, ob_session):
        """A user with no profile should get completed=False and step=1."""
        resp = ob_client_authed.get("/api/onboarding/status")
        assert resp.status_code == 200
        data = resp.json()
        assert data["completed"] is False
        assert data["step"] == 1
        assert data["profile"] is None

    def test_status_returns_not_completed_for_existing_incomplete_profile(self, ob_client_authed, ob_session):
        """A user with an existing profile but onboarding_completed=False → completed=False."""
        _make_profile(ob_session, _TEST_USER["sub"], display_name="Alice", onboarding_completed=False)
        resp = ob_client_authed.get("/api/onboarding/status")
        assert resp.status_code == 200
        data = resp.json()
        assert data["completed"] is False
        assert data["profile"]["display_name"] == "Alice"

    def test_status_returns_completed_when_done(self, ob_client_authed, ob_session):
        """A user with onboarding_completed=True → completed=True and step=5."""
        _make_profile(ob_session, _TEST_USER["sub"], onboarding_completed=True)
        resp = ob_client_authed.get("/api/onboarding/status")
        assert resp.status_code == 200
        data = resp.json()
        assert data["completed"] is True
        assert data["step"] == 5

    # ------------------------------------------------------------------
    # POST /api/onboarding/profile
    # ------------------------------------------------------------------

    def test_save_profile_updates_display_name(self, ob_client_authed, ob_session):
        """POST /profile should persist display_name to UserProfile."""
        resp = ob_client_authed.post(
            "/api/onboarding/profile",
            json={"display_name": "Jane Doe", "contact_email": "jane@example.com"},
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["display_name"] == "Jane Doe"
        assert data["contact_email"] == "jane@example.com"

        # Verify DB was actually updated
        profile = ob_session.query(UserProfile).filter(UserProfile.user_id == _TEST_USER["sub"]).first()
        assert profile is not None
        assert profile.display_name == "Jane Doe"
        assert profile.contact_email == "jane@example.com"

    def test_save_profile_requires_auth(self, ob_engine):
        """POST /profile without auth should return 401."""
        from app.main import app

        def override_db():
            Session = sessionmaker(bind=ob_engine)
            session = Session()
            try:
                yield session
            finally:
                session.close()

        app.dependency_overrides[get_db] = override_db
        with TestClient(app, base_url="http://localhost", raise_server_exceptions=False) as client:
            resp = client.post("/api/onboarding/profile", json={"display_name": "x"})
        app.dependency_overrides.clear()

        assert resp.status_code == 401

    def test_save_profile_null_fields_allowed(self, ob_client_authed, ob_session):
        """Sending null for display_name and contact_email should succeed."""
        resp = ob_client_authed.post(
            "/api/onboarding/profile",
            json={"display_name": None, "contact_email": None},
        )
        assert resp.status_code == 200

    # ------------------------------------------------------------------
    # POST /api/onboarding/plan
    # ------------------------------------------------------------------

    def test_save_plan_updates_subscription_tier(self, ob_client_authed, ob_session):
        """POST /plan should persist the chosen tier and billing cycle."""
        resp = ob_client_authed.post(
            "/api/onboarding/plan",
            json={"subscription_tier": "starter", "billing_cycle": "monthly"},
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["subscription_tier"] == "starter"
        assert data["subscription_billing_cycle"] == "monthly"

        profile = ob_session.query(UserProfile).filter(UserProfile.user_id == _TEST_USER["sub"]).first()
        assert profile.subscription_tier == "starter"

    def test_save_plan_yearly_billing(self, ob_client_authed, ob_session):
        """POST /plan with billing_cycle=yearly should persist correctly."""
        resp = ob_client_authed.post(
            "/api/onboarding/plan",
            json={"subscription_tier": "professional", "billing_cycle": "yearly"},
        )
        assert resp.status_code == 200
        assert resp.json()["subscription_billing_cycle"] == "yearly"

    def test_save_plan_rejects_invalid_tier(self, ob_client_authed):
        """POST /plan with an unknown tier should return 422."""
        resp = ob_client_authed.post(
            "/api/onboarding/plan",
            json={"subscription_tier": "unicorn", "billing_cycle": "monthly"},
        )
        assert resp.status_code == 422
        assert "Invalid subscription_tier" in resp.json()["detail"]

    def test_save_plan_rejects_invalid_billing_cycle(self, ob_client_authed):
        """POST /plan with an invalid billing_cycle value should return 422."""
        resp = ob_client_authed.post(
            "/api/onboarding/plan",
            json={"subscription_tier": "free", "billing_cycle": "weekly"},
        )
        assert resp.status_code == 422

    # ------------------------------------------------------------------
    # POST /api/onboarding/storage
    # ------------------------------------------------------------------

    def test_save_storage_updates_preferred_destination(self, ob_client_authed, ob_session):
        """POST /storage should persist the preferred_destination."""
        resp = ob_client_authed.post(
            "/api/onboarding/storage",
            json={"preferred_destination": "dropbox"},
        )
        assert resp.status_code == 200
        data = resp.json()
        assert data["preferred_destination"] == "dropbox"

        profile = ob_session.query(UserProfile).filter(UserProfile.user_id == _TEST_USER["sub"]).first()
        assert profile.preferred_destination == "dropbox"

    def test_save_storage_accepts_null_destination(self, ob_client_authed, ob_session):
        """POST /storage with null preferred_destination should succeed (skip storage)."""
        resp = ob_client_authed.post(
            "/api/onboarding/storage",
            json={"preferred_destination": None},
        )
        assert resp.status_code == 200
        assert resp.json()["preferred_destination"] is None

    # ------------------------------------------------------------------
    # POST /api/onboarding/complete
    # ------------------------------------------------------------------

    def test_complete_sets_onboarding_completed(self, ob_client_authed, ob_session):
        """POST /complete should set onboarding_completed=True on the profile."""
        _make_profile(ob_session, _TEST_USER["sub"])
        resp = ob_client_authed.post("/api/onboarding/complete")
        assert resp.status_code == 200
        data = resp.json()
        assert data["success"] is True
        assert "redirect_url" in data

        # Re-query to see persisted value
        ob_session.expire_all()
        profile = ob_session.query(UserProfile).filter(UserProfile.user_id == _TEST_USER["sub"]).first()
        assert profile.onboarding_completed is True
        assert profile.onboarding_completed_at is not None

    def test_complete_creates_profile_if_missing(self, ob_client_authed, ob_session):
        """POST /complete should create a profile when none exists and mark it done."""
        resp = ob_client_authed.post("/api/onboarding/complete")
        assert resp.status_code == 200

        profile = ob_session.query(UserProfile).filter(UserProfile.user_id == _TEST_USER["sub"]).first()
        assert profile is not None
        assert profile.onboarding_completed is True

    def test_complete_requires_auth(self, ob_engine):
        """POST /complete without auth should return 401."""
        from app.main import app

        def override_db():
            Session = sessionmaker(bind=ob_engine)
            session = Session()
            try:
                yield session
            finally:
                session.close()

        app.dependency_overrides[get_db] = override_db
        with TestClient(app, base_url="http://localhost", raise_server_exceptions=False) as client:
            resp = client.post("/api/onboarding/complete")
        app.dependency_overrides.clear()

        assert resp.status_code == 401
