"""Tests for organization management backend generator."""

import pytest

from prisme.generators import GeneratorContext
from prisme.generators.backend.organization import OrganizationGenerator
from prisme.spec import (
    AuthConfig,
    FieldSpec,
    FieldType,
    ModelSpec,
    StackSpec,
)
from prisme.spec.auth import OrganizationConfig, OrgRole
from prisme.spec.project import ProjectSpec
from prisme.spec.stack import FileStrategy


@pytest.fixture
def org_enabled_spec() -> StackSpec:
    """Stack spec with organization enabled."""
    return StackSpec(
        name="test-org-app",
        models=[
            ModelSpec(
                name="User",
                fields=[
                    FieldSpec(name="email", type=FieldType.STRING, required=True, unique=True),
                    FieldSpec(
                        name="password_hash", type=FieldType.STRING, required=True, hidden=True
                    ),
                    FieldSpec(name="is_active", type=FieldType.BOOLEAN, default=True),
                    FieldSpec(name="roles", type=FieldType.JSON, default=["user"]),
                ],
            )
        ],
    )


@pytest.fixture
def org_enabled_project() -> ProjectSpec:
    """Project spec with organization enabled."""
    return ProjectSpec(
        name="test-org-app",
        auth=AuthConfig(
            enabled=True,
            secret_key="${JWT_SECRET}",
            user_model="User",
            username_field="email",
            organization=OrganizationConfig(
                enabled=True,
                roles=[
                    OrgRole(name="owner", permissions=["*"]),
                    OrgRole(name="admin", permissions=["members.manage", "settings.edit"]),
                    OrgRole(name="member", permissions=["content.create"]),
                ],
                default_role="member",
            ),
        ),
    )


@pytest.fixture
def org_disabled_project() -> ProjectSpec:
    """Project spec with organization disabled."""
    return ProjectSpec(
        name="test-app",
        auth=AuthConfig(enabled=True, secret_key="${JWT_SECRET}"),
    )


@pytest.fixture
def auth_disabled_spec() -> StackSpec:
    """Stack spec with auth disabled entirely."""
    return StackSpec(
        name="test-app",
        models=[
            ModelSpec(
                name="Post",
                fields=[FieldSpec(name="title", type=FieldType.STRING, required=True)],
            )
        ],
    )


class TestOrganizationGenerator:
    """Tests for OrganizationGenerator."""

    def test_skips_when_org_disabled(self, org_enabled_spec, org_disabled_project, tmp_path):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_disabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()
        assert files == []
        assert generator.skip_generation is True

    def test_skips_when_auth_disabled(self, auth_disabled_spec, tmp_path):
        context = GeneratorContext(
            domain_spec=auth_disabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=ProjectSpec(name="test-app"),
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()
        assert files == []
        assert generator.skip_generation is True

    def test_generates_correct_file_count(self, org_enabled_spec, org_enabled_project, tmp_path):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()
        # org_model, membership_model, invitation_model, service, schemas, routes, middleware, init
        assert len(files) == 8

    def test_generates_correct_paths(self, org_enabled_spec, org_enabled_project, tmp_path):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()
        file_paths = [str(f.path) for f in files]

        assert any("org_model.py" in p for p in file_paths)
        assert any("org_membership_model.py" in p for p in file_paths)
        assert any("org_invitation_model.py" in p for p in file_paths)
        assert any("org_service.py" in p for p in file_paths)
        assert any("schemas" in p and "organization.py" in p for p in file_paths)
        assert any("rest" in p and "organization.py" in p for p in file_paths)
        assert any("middleware" in p and "organization.py" in p for p in file_paths)
        assert any("__init__.py" in p for p in file_paths)

    def test_org_model_has_correct_columns(self, org_enabled_spec, org_enabled_project, tmp_path):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()

        model_file = next(f for f in files if "org_model.py" in str(f.path))
        content = model_file.content

        assert "class Organization" in content
        assert "organizations" in content  # table name
        assert "name" in content
        assert "slug" in content
        assert "description" in content
        assert "is_active" in content
        assert "created_at" in content

    def test_membership_model_has_correct_structure(
        self, org_enabled_spec, org_enabled_project, tmp_path
    ):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()

        model_file = next(f for f in files if "org_membership_model.py" in str(f.path))
        content = model_file.content

        assert "class OrganizationMembership" in content
        assert "organization_id" in content
        assert "user_id" in content
        assert "role" in content

    def test_invitation_model_has_correct_structure(
        self, org_enabled_spec, org_enabled_project, tmp_path
    ):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()

        model_file = next(f for f in files if "org_invitation_model.py" in str(f.path))
        content = model_file.content

        assert "class OrganizationInvitation" in content
        assert "email" in content
        assert "token" in content
        assert "expires_at" in content

    def test_routes_contain_expected_endpoints(
        self, org_enabled_spec, org_enabled_project, tmp_path
    ):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()

        route_file = next(
            f for f in files if "rest" in str(f.path) and "organization.py" in str(f.path)
        )
        content = route_file.content

        assert 'prefix="/organizations"' in content
        assert "create_org" in content
        assert "list_my_orgs" in content
        assert "get_org" in content
        assert "update_org" in content
        assert "delete_org" in content
        assert "list_org_members" in content
        assert "add_org_member" in content
        assert "update_org_member_role" in content
        assert "remove_org_member" in content
        assert "invite_to_org" in content
        assert "accept_org_invitation" in content
        assert "leave_org" in content
        assert "list_org_roles" in content

    def test_service_has_rbac_functions(self, org_enabled_spec, org_enabled_project, tmp_path):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()

        service_file = next(f for f in files if "org_service.py" in str(f.path))
        content = service_file.content

        assert "ORG_ROLES" in content
        assert "has_org_permission" in content
        assert "create_organization" in content
        assert "add_member" in content
        assert "update_member_role" in content
        assert "remove_member" in content
        assert "create_invitation" in content
        assert "accept_invitation" in content
        # Check role definitions are present
        assert '"owner"' in content
        assert '"admin"' in content
        assert '"member"' in content

    def test_middleware_has_permission_checks(
        self, org_enabled_spec, org_enabled_project, tmp_path
    ):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()

        middleware_file = next(
            f for f in files if "middleware" in str(f.path) and "organization.py" in str(f.path)
        )
        content = middleware_file.content

        assert "get_org_membership" in content
        assert "require_org_permission" in content
        assert "403" in content

    def test_file_strategies(self, org_enabled_spec, org_enabled_project, tmp_path):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()

        # Routes should be GENERATE_ONCE
        route_file = next(
            f for f in files if "rest" in str(f.path) and "organization.py" in str(f.path)
        )
        assert route_file.strategy == FileStrategy.GENERATE_ONCE

        # Models should be ALWAYS_OVERWRITE
        model_file = next(f for f in files if "org_model.py" in str(f.path))
        assert model_file.strategy == FileStrategy.ALWAYS_OVERWRITE

        # Service should be ALWAYS_OVERWRITE
        service_file = next(f for f in files if "org_service.py" in str(f.path))
        assert service_file.strategy == FileStrategy.ALWAYS_OVERWRITE

    def test_uses_correct_project_name(self, org_enabled_spec, org_enabled_project, tmp_path):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()

        route_file = next(
            f for f in files if "rest" in str(f.path) and "organization.py" in str(f.path)
        )
        assert "from test_org_app." in route_file.content

    def test_schemas_have_correct_types(self, org_enabled_spec, org_enabled_project, tmp_path):
        context = GeneratorContext(
            domain_spec=org_enabled_spec,
            output_dir=tmp_path,
            dry_run=True,
            project_spec=org_enabled_project,
        )
        generator = OrganizationGenerator(context)
        files = generator.generate_files()

        schema_file = next(
            f for f in files if "schemas" in str(f.path) and "organization.py" in str(f.path)
        )
        content = schema_file.content

        assert "OrganizationCreateRequest" in content
        assert "OrganizationUpdateRequest" in content
        assert "OrganizationResponse" in content
        assert "MemberResponse" in content
        assert "InvitationCreateRequest" in content
        assert "InvitationResponse" in content
        assert "UpdateMemberRoleRequest" in content
        assert "OrgRoleResponse" in content
