"""Tests for the frontend auth generator."""

from __future__ import annotations

from pathlib import Path

import pytest

from prisme.generators.base import GeneratorContext
from prisme.generators.frontend.auth import FrontendAuthGenerator
from prisme.spec.auth import AuthConfig, OAuthProviderConfig
from prisme.spec.fields import FieldSpec, FieldType
from prisme.spec.model import ModelSpec
from prisme.spec.project import ProjectSpec
from prisme.spec.stack import FileStrategy, StackSpec


@pytest.fixture
def user_model() -> ModelSpec:
    return ModelSpec(
        name="User",
        fields=[
            FieldSpec(name="email", type=FieldType.STRING, required=True),
            FieldSpec(name="password_hash", type=FieldType.STRING, hidden=True),
            FieldSpec(name="is_active", type=FieldType.BOOLEAN, default=True),
            FieldSpec(name="roles", type=FieldType.JSON, default=[]),
        ],
    )


@pytest.fixture
def stack_with_user(user_model: ModelSpec) -> StackSpec:
    return StackSpec(
        name="test-project",
        version="1.0.0",
        models=[user_model],
    )


@pytest.fixture
def auth_project_spec() -> ProjectSpec:
    return ProjectSpec(
        name="test-project",
        auth=AuthConfig(
            enabled=True,
            username_field="email",
            password_reset=True,
            email_verification=True,
            mfa_enabled=True,
            oauth_providers=[
                OAuthProviderConfig(
                    provider="google", client_id_env="GOOGLE_ID", client_secret_env="GOOGLE_SECRET"
                ),
            ],
        ),
    )


@pytest.fixture
def auth_context(
    stack_with_user: StackSpec, auth_project_spec: ProjectSpec, tmp_path: Path
) -> GeneratorContext:
    return GeneratorContext(
        domain_spec=stack_with_user,
        output_dir=tmp_path,
        dry_run=True,
        project_spec=auth_project_spec,
    )


class TestFrontendAuthGeneratorDisabled:
    """Tests when auth is disabled."""

    def test_skip_when_auth_disabled(self, stack_with_user: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(
            domain_spec=stack_with_user,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                auth=AuthConfig(enabled=False),
            ),
        )
        gen = FrontendAuthGenerator(ctx)
        assert gen.generate_files() == []


class TestFrontendAuthGeneratorFull:
    """Tests for full auth generation with all features."""

    def test_generates_auth_api(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        api_file = next((f for f in files if "authApi.ts" in str(f.path)), None)
        assert api_file is not None
        assert api_file.strategy == FileStrategy.ALWAYS_OVERWRITE

    def test_generates_auth_context(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        ctx_file = next((f for f in files if "AuthContext.tsx" in str(f.path)), None)
        assert ctx_file is not None

    def test_generates_login_form(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        login = next((f for f in files if "LoginForm.tsx" in str(f.path)), None)
        assert login is not None

    def test_generates_signup_form(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        signup = next((f for f in files if "SignupForm.tsx" in str(f.path)), None)
        assert signup is not None

    def test_generates_protected_route(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        pr = next((f for f in files if "ProtectedRoute.tsx" in str(f.path)), None)
        assert pr is not None

    def test_generates_login_page(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        page = next((f for f in files if f.path.name == "Login.tsx"), None)
        assert page is not None

    def test_generates_signup_page(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        page = next((f for f in files if f.path.name == "Signup.tsx"), None)
        assert page is not None

    def test_generates_forgot_password(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        forgot = next((f for f in files if "ForgotPasswordForm.tsx" in str(f.path)), None)
        assert forgot is not None

    def test_generates_reset_password(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        reset = next((f for f in files if "ResetPassword.tsx" in str(f.path)), None)
        assert reset is not None

    def test_generates_verify_email(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        verify = next((f for f in files if "VerifyEmail.tsx" in str(f.path)), None)
        assert verify is not None

    def test_generates_email_verification_component(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        ev = next((f for f in files if "EmailVerification.tsx" in str(f.path)), None)
        assert ev is not None

    def test_generates_totp_verify(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        totp = next((f for f in files if "TOTPVerify.tsx" in str(f.path)), None)
        assert totp is not None

    def test_generates_auth_callback(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        callback = next((f for f in files if "AuthCallback.tsx" in str(f.path)), None)
        assert callback is not None

    def test_generates_components_index(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        index = next((f for f in files if f.path.name == "index.ts"), None)
        assert index is not None

    def test_total_file_count_full_auth(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        files = gen.generate_files()
        # 7 base (api, context, login form, signup form, protected route, login page, signup page)
        # + 2 password_reset (forgot, reset)
        # + 2 email_verification (verify email page, email verification component)
        # + 1 mfa (totp)
        # + 1 oauth (callback)
        # + 1 index
        assert len(files) == 14


class TestFrontendAuthGeneratorPartial:
    """Tests for partial auth features."""

    def test_no_password_reset(self, stack_with_user: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(
            domain_spec=stack_with_user,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                auth=AuthConfig(enabled=True, password_reset=False),
            ),
        )
        gen = FrontendAuthGenerator(ctx)
        files = gen.generate_files()
        assert not any("ForgotPasswordForm" in str(f.path) for f in files)
        assert not any("ResetPassword" in str(f.path) for f in files)

    def test_no_email_verification(self, stack_with_user: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(
            domain_spec=stack_with_user,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                auth=AuthConfig(enabled=True, email_verification=False),
            ),
        )
        gen = FrontendAuthGenerator(ctx)
        files = gen.generate_files()
        assert not any("VerifyEmail" in str(f.path) for f in files)
        assert not any("EmailVerification" in str(f.path) for f in files)

    def test_no_mfa(self, stack_with_user: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(
            domain_spec=stack_with_user,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                auth=AuthConfig(enabled=True, mfa_enabled=False),
            ),
        )
        gen = FrontendAuthGenerator(ctx)
        files = gen.generate_files()
        assert not any("TOTPVerify" in str(f.path) for f in files)

    def test_no_oauth(self, stack_with_user: StackSpec, tmp_path: Path) -> None:
        ctx = GeneratorContext(
            domain_spec=stack_with_user,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                auth=AuthConfig(enabled=True, oauth_providers=[]),
            ),
        )
        gen = FrontendAuthGenerator(ctx)
        files = gen.generate_files()
        assert not any("AuthCallback" in str(f.path) for f in files)

    def test_minimal_auth_generates_base_files(
        self, stack_with_user: StackSpec, tmp_path: Path
    ) -> None:
        ctx = GeneratorContext(
            domain_spec=stack_with_user,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(
                name="test-project",
                auth=AuthConfig(
                    enabled=True,
                    password_reset=False,
                    email_verification=False,
                    mfa_enabled=False,
                    oauth_providers=[],
                ),
            ),
        )
        gen = FrontendAuthGenerator(ctx)
        files = gen.generate_files()
        # 7 base + 1 index
        assert len(files) == 8


class TestFrontendAuthGeneratorContext:
    """Tests for template context building."""

    def test_common_context_email_field(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        ctx = gen._get_common_context()
        assert ctx["username_field"] == "email"
        assert ctx["is_email"] is True
        assert ctx["input_type"] == "email"

    def test_common_context_username_field(
        self, stack_with_user: StackSpec, tmp_path: Path
    ) -> None:
        project = ProjectSpec(
            name="test-project",
            auth=AuthConfig(enabled=True, username_field="username"),
        )
        ctx = GeneratorContext(
            domain_spec=stack_with_user,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=project,
        )
        gen = FrontendAuthGenerator(ctx)
        common = gen._get_common_context()
        assert common["username_field"] == "username"
        assert common["is_email"] is False
        assert common["input_type"] == "text"

    def test_common_context_feature_flags(self, auth_context: GeneratorContext) -> None:
        gen = FrontendAuthGenerator(auth_context)
        ctx = gen._get_common_context()
        assert ctx["email_verification"] is True
        assert ctx["password_reset"] is True
        assert ctx["mfa_enabled"] is True
        assert len(ctx["oauth_providers"]) == 1
