"""Organization service.

⚠️ AUTO-GENERATED BY PRISM - DO NOT EDIT

Business logic for organization CRUD, member management, and invitations.
"""

from __future__ import annotations

import logging
from datetime import UTC, datetime, timedelta

from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from {{ project_name }}.organization.org_model import Organization
from {{ project_name }}.organization.org_membership_model import OrganizationMembership
from {{ project_name }}.organization.org_invitation_model import OrganizationInvitation

logger = logging.getLogger(__name__)

# ── Organization role definitions ────────────────────────────────

ORG_ROLES = {
{% for role in org_roles %}
    "{{ role.name }}": {{ role.permissions }},
{% endfor %}
}

DEFAULT_ORG_ROLE = "{{ default_org_role }}"


def has_org_permission(role: str, permission: str) -> bool:
    """Check if an org role has a specific permission."""
    permissions = ORG_ROLES.get(role, [])
    return "*" in permissions or permission in permissions


# ── Organization CRUD ────────────────────────────────────────────


async def create_organization(
    db: AsyncSession,
    *,
    name: str,
    slug: str | None = None,
    description: str | None = None,
    created_by_id: int | None = None,
) -> Organization:
    """Create a new organization and add the creator as owner."""
    if slug is None:
        slug = Organization.generate_slug(name)

    # Check slug uniqueness
    existing = await db.execute(
        select(Organization).where(Organization.{{ slug_field }} == slug)
    )
    if existing.scalar_one_or_none():
        # Append a suffix to make it unique
        import secrets
        slug = f"{slug}-{secrets.token_hex(3)}"

    org = Organization(
        name=name,
        {{ slug_field }}=slug,
        description=description,
        created_by_id=created_by_id,
    )
    db.add(org)
    await db.flush()

    # Add creator as owner
    if created_by_id is not None:
        membership = OrganizationMembership(
            organization_id=org.id,
            user_id=created_by_id,
            role="owner",
        )
        db.add(membership)

    await db.commit()
    await db.refresh(org)
    return org


async def get_organization_by_id(db: AsyncSession, org_id: int) -> Organization | None:
    """Get an organization by ID."""
    result = await db.execute(select(Organization).where(Organization.id == org_id))
    return result.scalar_one_or_none()


async def get_organization_by_slug(db: AsyncSession, slug: str) -> Organization | None:
    """Get an organization by slug."""
    result = await db.execute(
        select(Organization).where(Organization.{{ slug_field }} == slug)
    )
    return result.scalar_one_or_none()


async def list_organizations(
    db: AsyncSession, *, skip: int = 0, limit: int = 50
) -> list[Organization]:
    """List all organizations."""
    result = await db.execute(
        select(Organization)
        .where(Organization.is_active.is_(True))
        .order_by(Organization.created_at.desc())
        .offset(skip)
        .limit(limit)
    )
    return list(result.scalars().all())


async def list_user_organizations(
    db: AsyncSession, user_id: int
) -> list[tuple[Organization, str]]:
    """List organizations a user belongs to, with their role."""
    result = await db.execute(
        select(Organization, OrganizationMembership.role)
        .join(OrganizationMembership)
        .where(
            OrganizationMembership.user_id == user_id,
            OrganizationMembership.is_active.is_(True),
            Organization.is_active.is_(True),
        )
        .order_by(Organization.name)
    )
    return list(result.all())


async def update_organization(
    db: AsyncSession,
    org_id: int,
    *,
    name: str | None = None,
    description: str | None = None,
    avatar_url: str | None = None,
) -> Organization | None:
    """Update organization details."""
    org = await get_organization_by_id(db, org_id)
    if not org:
        return None

    if name is not None:
        org.name = name
    if description is not None:
        org.description = description
    if avatar_url is not None:
        org.avatar_url = avatar_url

    await db.commit()
    await db.refresh(org)
    return org


async def delete_organization(db: AsyncSession, org_id: int) -> bool:
    """Soft-delete an organization by marking it inactive."""
    org = await get_organization_by_id(db, org_id)
    if not org:
        return False
    org.is_active = False
    await db.commit()
    return True


async def count_organizations(db: AsyncSession) -> int:
    """Count active organizations."""
    result = await db.execute(
        select(func.count(Organization.id)).where(Organization.is_active.is_(True))
    )
    return result.scalar_one()


# ── Member management ────────────────────────────────────────────


async def get_membership(
    db: AsyncSession, org_id: int, user_id: int
) -> OrganizationMembership | None:
    """Get a user's membership in an organization."""
    result = await db.execute(
        select(OrganizationMembership).where(
            OrganizationMembership.organization_id == org_id,
            OrganizationMembership.user_id == user_id,
        )
    )
    return result.scalar_one_or_none()


async def list_members(
    db: AsyncSession, org_id: int, *, skip: int = 0, limit: int = 50
) -> list[OrganizationMembership]:
    """List members of an organization."""
    result = await db.execute(
        select(OrganizationMembership)
        .where(
            OrganizationMembership.organization_id == org_id,
            OrganizationMembership.is_active.is_(True),
        )
        .order_by(OrganizationMembership.joined_at)
        .offset(skip)
        .limit(limit)
    )
    return list(result.scalars().all())


async def count_members(db: AsyncSession, org_id: int) -> int:
    """Count active members of an organization."""
    result = await db.execute(
        select(func.count(OrganizationMembership.id)).where(
            OrganizationMembership.organization_id == org_id,
            OrganizationMembership.is_active.is_(True),
        )
    )
    return result.scalar_one()


async def add_member(
    db: AsyncSession,
    org_id: int,
    user_id: int,
    role: str = DEFAULT_ORG_ROLE,
) -> OrganizationMembership:
    """Add a user to an organization."""
{% if max_members_per_org > 0 %}
    # Check member limit
    current_count = await count_members(db, org_id)
    if current_count >= {{ max_members_per_org }}:
        raise ValueError("Organization has reached its member limit ({{ max_members_per_org }})")
{% endif %}
{% if max_orgs_per_user > 0 %}
    # Check user org limit
    user_org_count = await db.execute(
        select(func.count(OrganizationMembership.id)).where(
            OrganizationMembership.user_id == user_id,
            OrganizationMembership.is_active.is_(True),
        )
    )
    if user_org_count.scalar_one() >= {{ max_orgs_per_user }}:
        raise ValueError("User has reached their organization limit ({{ max_orgs_per_user }})")
{% endif %}

    # Check for existing membership
    existing = await get_membership(db, org_id, user_id)
    if existing:
        if existing.is_active:
            raise ValueError("User is already a member of this organization")
        # Reactivate
        existing.is_active = True
        existing.role = role
        await db.commit()
        await db.refresh(existing)
        return existing

    membership = OrganizationMembership(
        organization_id=org_id,
        user_id=user_id,
        role=role,
    )
    db.add(membership)
    await db.commit()
    await db.refresh(membership)
    return membership


async def update_member_role(
    db: AsyncSession, org_id: int, user_id: int, new_role: str
) -> OrganizationMembership | None:
    """Update a member's role in an organization."""
    if new_role not in ORG_ROLES:
        raise ValueError(f"Invalid role: {new_role}. Valid roles: {list(ORG_ROLES.keys())}")

    membership = await get_membership(db, org_id, user_id)
    if not membership or not membership.is_active:
        return None

    membership.role = new_role
    await db.commit()
    await db.refresh(membership)
    return membership


async def remove_member(db: AsyncSession, org_id: int, user_id: int) -> bool:
    """Remove a member from an organization (soft-delete)."""
    membership = await get_membership(db, org_id, user_id)
    if not membership or not membership.is_active:
        return False

    # Prevent removing the last owner
    if membership.role == "owner":
        owner_count = await db.execute(
            select(func.count(OrganizationMembership.id)).where(
                OrganizationMembership.organization_id == org_id,
                OrganizationMembership.role == "owner",
                OrganizationMembership.is_active.is_(True),
            )
        )
        if owner_count.scalar_one() <= 1:
            raise ValueError("Cannot remove the last owner of an organization")

    membership.is_active = False
    await db.commit()
    return True


# ── Invitation management ────────────────────────────────────────


async def create_invitation(
    db: AsyncSession,
    org_id: int,
    email: str,
    role: str = DEFAULT_ORG_ROLE,
    invited_by_id: int | None = None,
) -> OrganizationInvitation:
    """Create an invitation to join an organization."""
    # Check for existing pending invitation
    result = await db.execute(
        select(OrganizationInvitation).where(
            OrganizationInvitation.organization_id == org_id,
            OrganizationInvitation.email == email.lower(),
            OrganizationInvitation.expires_at > datetime.now(UTC),
        )
    )
    existing = result.scalar_one_or_none()
    if existing:
        raise ValueError(f"A pending invitation already exists for {email}")

    invitation = OrganizationInvitation(
        organization_id=org_id,
        email=email.lower(),
        role=role,
        invited_by_id=invited_by_id,
    )
    db.add(invitation)
    await db.commit()
    await db.refresh(invitation)
    return invitation


async def get_invitation_by_token(
    db: AsyncSession, token: str
) -> OrganizationInvitation | None:
    """Get an invitation by its token."""
    result = await db.execute(
        select(OrganizationInvitation).where(
            OrganizationInvitation.token == token,
        )
    )
    return result.scalar_one_or_none()


async def accept_invitation(
    db: AsyncSession, token: str, user_id: int
) -> OrganizationMembership:
    """Accept an invitation and create a membership."""
    invitation = await get_invitation_by_token(db, token)
    if not invitation:
        raise ValueError("Invalid invitation token")
    if invitation.is_expired:
        raise ValueError("Invitation has expired")

    membership = await add_member(
        db, invitation.organization_id, user_id, role=invitation.role
    )

    # Delete the invitation
    await db.delete(invitation)
    await db.commit()

    return membership


async def list_org_invitations(
    db: AsyncSession, org_id: int
) -> list[OrganizationInvitation]:
    """List pending invitations for an organization."""
    result = await db.execute(
        select(OrganizationInvitation)
        .where(
            OrganizationInvitation.organization_id == org_id,
            OrganizationInvitation.expires_at > datetime.now(UTC),
        )
        .order_by(OrganizationInvitation.created_at.desc())
    )
    return list(result.scalars().all())


async def revoke_invitation(db: AsyncSession, invitation_id: int) -> bool:
    """Revoke a pending invitation."""
    result = await db.execute(
        select(OrganizationInvitation).where(
            OrganizationInvitation.id == invitation_id,
        )
    )
    invitation = result.scalar_one_or_none()
    if not invitation:
        return False
    await db.delete(invitation)
    await db.commit()
    return True
