"""
Organizations V2 - Redesigned backoffice interface with improved UX.

This module provides a modern, three-column layout with:
- Enhanced list view with status tabs and smart grouping
- Progressive disclosure in detail views
- Contextual actions based on organization status
- Keyboard shortcuts and accessibility improvements
"""

from datetime import UTC, datetime

import structlog
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from pydantic import UUID4, ValidationError
from pydantic_core import PydanticCustomError
from sqlalchemy import and_, or_, select
from sqlalchemy.orm import joinedload
from tagflow import tag, text

from polar.account.service import account as account_service
from polar.auth.scope import Scope
from polar.auth.service import auth as auth_service
from polar.backoffice.organizations.analytics import (
    OrganizationSetupAnalyticsService,
    PaymentAnalyticsService,
)
from polar.backoffice.organizations.forms import (
    DeleteStripeAccountForm,
    DisconnectStripeAccountForm,
    UpdateOrganizationBasicForm,
    UpdateOrganizationDetailsForm,
    UpdateOrganizationInternalNotesForm,
    UpdateOrganizationSocialsForm,
)
from polar.enums import AccountType
from polar.models import Organization, User, UserOrganization
from polar.models.organization import OrganizationStatus
from polar.models.transaction import TransactionType
from polar.models.user import IdentityVerificationStatus
from polar.organization.repository import OrganizationRepository
from polar.organization.schemas import OrganizationFeatureSettings
from polar.organization.service import organization as organization_service
from polar.postgres import AsyncSession, get_db_session
from polar.transaction.service.transaction import transaction as transaction_service

from ..components import button, modal
from ..layout import layout
from ..responses import HXRedirectResponse
from .views.detail_view import OrganizationDetailView
from .views.list_view import OrganizationListView
from .views.modals import DeleteStripeModal, DisconnectStripeModal
from .views.sections.account_section import AccountSection
from .views.sections.files_section import FilesSection
from .views.sections.overview_section import OverviewSection
from .views.sections.settings_section import SettingsSection
from .views.sections.team_section import TeamSection

router = APIRouter(prefix="/organizations-v2", tags=["organizations-v2"])

logger = structlog.getLogger(__name__)


@router.get("/", name="organizations-v2:list")
async def list_organizations(
    request: Request,
    session: AsyncSession = Depends(get_db_session),
    status: str | None = Query(None),
    q: str | None = Query(None),
    sort: str = Query("priority"),
    direction: str = Query("asc"),
    page: int = Query(1, ge=1),
    limit: int = Query(50, ge=1, le=100),
    # Advanced filters
    country: str | None = Query(""),
    risk_level: str | None = Query(""),
    days_in_status: str | None = Query(""),
    has_appeal: str | None = Query(""),
) -> None:
    """
    List organizations with enhanced filtering and smart grouping.

    Features:
    - Status-based tabs with counts
    - "Needs Attention" smart grouping
    - Search across name, slug, email
    - Advanced filters: country, risk, transfers, payments, appeals
    - Column sorting
    """
    from datetime import UTC, datetime, timedelta

    from polar.models import Account
    from polar.models.organization_review import OrganizationReview

    repository = OrganizationRepository(session)
    list_view = OrganizationListView(session)

    # Convert empty strings to None and parse numbers
    country = country if country else None
    risk_level = risk_level if risk_level else None
    has_appeal = has_appeal if has_appeal else None
    days_in_status_int = int(days_in_status) if days_in_status else None

    # Parse status filter
    status_filter: OrganizationStatus | None = None
    if status == "active":
        status_filter = OrganizationStatus.ACTIVE
    elif status == "denied":
        status_filter = OrganizationStatus.DENIED
    elif status == "initial_review":
        status_filter = OrganizationStatus.INITIAL_REVIEW
    elif status == "ongoing_review":
        status_filter = OrganizationStatus.ONGOING_REVIEW
    elif status == "created":
        status_filter = OrganizationStatus.CREATED
    elif status == "onboarding_started":
        status_filter = OrganizationStatus.ONBOARDING_STARTED

    # Build query
    stmt = select(Organization).options(
        joinedload(Organization.account),
        joinedload(Organization.review),
    )

    # Apply filters
    if status_filter:
        stmt = stmt.where(Organization.status == status_filter)
    else:
        # By default, exclude denied organizations
        stmt = stmt.where(Organization.status != OrganizationStatus.DENIED)

    if q:
        search_term = f"%{q}%"
        stmt = stmt.where(
            or_(
                Organization.name.ilike(search_term),
                Organization.slug.ilike(search_term),
                Organization.email.ilike(search_term),
            )
        )

    # Country filter
    if country:
        stmt = stmt.join(Organization.account).where(Account.country == country)

    # Risk level filter
    if risk_level:
        stmt = stmt.join(Organization.review)
        if risk_level == "high":
            stmt = stmt.where(OrganizationReview.risk_score >= 75)
        elif risk_level == "medium":
            stmt = stmt.where(
                OrganizationReview.risk_score >= 50, OrganizationReview.risk_score < 75
            )
        elif risk_level == "low":
            stmt = stmt.where(OrganizationReview.risk_score < 50)
        elif risk_level == "unscored":
            stmt = (
                select(Organization)
                .options(
                    joinedload(Organization.account),
                    joinedload(Organization.review),
                )
                .outerjoin(Organization.review)
                .where(OrganizationReview.id.is_(None))
            )

    # Days in status filter
    if days_in_status_int:
        threshold_date = datetime.now(UTC) - timedelta(days=days_in_status_int)
        # Organizations in status for more than X days:
        # - Either status_updated_at exists and is older than threshold
        # - Or status_updated_at is NULL (never changed) and created_at is older than threshold
        stmt = stmt.where(
            or_(
                Organization.status_updated_at <= threshold_date,
                and_(
                    Organization.status_updated_at.is_(None),
                    Organization.created_at <= threshold_date,
                ),
            )
        )

    # Appeal filter
    if has_appeal:
        stmt = stmt.join(Organization.review)
        if has_appeal == "pending":
            stmt = stmt.where(
                OrganizationReview.appeal_submitted_at.is_not(None),
                OrganizationReview.appeal_reviewed_at.is_(None),
            )
        elif has_appeal == "reviewed":
            stmt = stmt.where(OrganizationReview.appeal_reviewed_at.is_not(None))
        elif has_appeal == "none":
            stmt = stmt.where(OrganizationReview.appeal_submitted_at.is_(None))

    # Apply sorting
    is_desc = direction == "desc"

    if sort == "name":
        stmt = stmt.order_by(
            Organization.name.desc() if is_desc else Organization.name.asc()
        )
    elif sort == "country":
        country_order = (
            Account.country.desc().nullslast()
            if is_desc
            else Account.country.asc().nullslast()
        )
        stmt = stmt.join(Organization.account).order_by(country_order)
    elif sort == "created":
        stmt = stmt.order_by(
            Organization.created_at.asc() if is_desc else Organization.created_at.desc()
        )
    elif sort == "updated":
        stmt = stmt.order_by(
            Organization.modified_at.asc()
            if is_desc
            else Organization.modified_at.desc()
        )
    elif sort == "status_duration":
        status_order = (
            Organization.status_updated_at.desc().nullslast()
            if is_desc
            else Organization.status_updated_at.asc().nullsfirst()
        )
        stmt = stmt.order_by(status_order)
    elif sort == "risk":
        risk_order = (
            OrganizationReview.risk_score.asc().nullsfirst()
            if is_desc
            else OrganizationReview.risk_score.desc().nullslast()
        )
        stmt = stmt.join(Organization.review).order_by(risk_order)
    elif sort == "next_review":
        threshold_order = (
            Organization.next_review_threshold.asc().nullsfirst()
            if is_desc
            else Organization.next_review_threshold.desc().nullslast()
        )
        stmt = stmt.order_by(threshold_order)
    elif sort == "priority":
        # Priority: Under Review > Denied > Others, then by days in status
        stmt = stmt.order_by(
            Organization.status.desc(),
            Organization.status_updated_at.asc().nullsfirst(),
        )

    # Pagination
    offset = (page - 1) * limit
    stmt = stmt.offset(offset).limit(limit + 1)

    result = await session.execute(stmt)
    organizations = list(result.scalars().unique().all())

    # Check if there are more results
    has_more = len(organizations) > limit
    if has_more:
        organizations = organizations[:limit]

    # Get status counts for tabs
    status_counts = await list_view.get_status_counts()

    # Get distinct countries for filter dropdown
    countries = await list_view.get_distinct_countries()

    # Check if this is an HTMX request targeting just the table
    is_htmx_table_request = request.headers.get("HX-Target") == "org-list"

    if is_htmx_table_request:
        # Only return the table content
        with list_view.render_table_only(
            request,
            organizations,
            status_filter,
            status_counts,
            page,
            has_more,
            sort,
            direction,
        ):
            pass
    else:
        # Render full page with layout
        with layout(
            request,
            [("Organizations V2", str(request.url))],
            "organizations-v2:list",
        ):
            with list_view.render(
                request,
                organizations,
                status_filter,
                status_counts,
                page,
                has_more,
                sort,
                direction,
                countries,
                country,
            ):
                pass


@router.get("/{organization_id}", name="organizations-v2:detail")
async def get_organization_detail(
    request: Request,
    organization_id: UUID4,
    section: str = Query("overview"),
    session: AsyncSession = Depends(get_db_session),
) -> None:
    """
    Organization detail view with three-column layout.

    Features:
    - Left sidebar: Section navigation
    - Main content: Current section details
    - Right sidebar: Contextual actions and metadata
    """
    repository = OrganizationRepository(session)

    # Fetch organization with relationships
    stmt = (
        select(Organization)
        .options(
            joinedload(Organization.account),
            joinedload(Organization.review),
        )
        .where(Organization.id == organization_id)
    )

    result = await session.execute(stmt)
    organization = result.scalars().unique().one_or_none()

    if not organization:
        raise HTTPException(status_code=404, detail="Organization not found")

    # Fetch members separately
    members_stmt = (
        select(UserOrganization)
        .options(joinedload(UserOrganization.user))
        .where(UserOrganization.organization_id == organization_id)
        .limit(10)
    )
    members_result = await session.execute(members_stmt)
    organization.members = list(members_result.scalars().unique().all())  # type: ignore[attr-defined]

    # Create views
    detail_view = OrganizationDetailView(organization)

    # Fetch analytics data for overview section
    setup_data = None
    payment_stats = None
    if section == "overview":
        setup_analytics = OrganizationSetupAnalyticsService(session)
        payment_analytics = PaymentAnalyticsService(session)

        # Get setup metrics
        checkout_links_count = await setup_analytics.get_checkout_links_count(
            organization_id
        )
        webhooks_count = await setup_analytics.get_webhooks_count(organization_id)
        api_keys_count = await setup_analytics.get_organization_tokens_count(
            organization_id
        )
        products_count = await setup_analytics.get_products_count(organization_id)
        benefits_count = await setup_analytics.get_benefits_count(organization_id)

        user_verified_result = await session.execute(
            select(User.identity_verification_status)
            .join(UserOrganization, User.id == UserOrganization.user_id)
            .where(UserOrganization.organization_id == organization_id)
            .limit(1)
        )
        user_verified_row = user_verified_result.first()
        user_verified = (
            user_verified_row[0] == IdentityVerificationStatus.verified
            if user_verified_row
            else False
        )

        (
            account_charges_enabled,
            account_payouts_enabled,
        ) = await setup_analytics.check_account_enabled(organization)
        payment_ready = await organization_service.is_organization_ready_for_payment(
            session, organization
        )

        setup_score = OrganizationSetupAnalyticsService.calculate_setup_score(
            checkout_links_count,
            webhooks_count,
            api_keys_count,
            products_count,
            benefits_count,
            user_verified,
            account_charges_enabled,
            account_payouts_enabled,
        )

        # Calculate total transfer sum (balance transactions)
        total_transfer_sum = 0
        if organization.account_id:
            total_transfer_sum = await transaction_service.get_transactions_sum(
                session, organization.account_id, type=TransactionType.balance
            )
        else:
            logger.warning(
                "Organization has no account_id for transaction sum",
                organization_id=str(organization.id),
                organization_slug=organization.slug,
            )

        setup_data = {
            "setup_score": setup_score,
            "checkout_links_count": checkout_links_count,
            "webhooks_count": webhooks_count,
            "api_keys_count": api_keys_count,
            "products_count": products_count,
            "benefits_count": benefits_count,
            "user_verified": user_verified,
            "account_charges_enabled": account_charges_enabled,
            "account_payouts_enabled": account_payouts_enabled,
            "payment_ready": payment_ready,
            "next_review_threshold": organization.next_review_threshold,
            "total_transfer_sum": total_transfer_sum,
        }

        # Get payment metrics
        (
            payment_count,
            total_amount,
            risk_scores,
        ) = await payment_analytics.get_succeeded_payments_stats(organization_id)
        refunds_count, refunds_amount = await payment_analytics.get_refund_stats(
            organization_id
        )

        p50_risk, p90_risk = PaymentAnalyticsService.calculate_risk_percentiles(
            risk_scores
        )
        refund_rate = (refunds_count / payment_count * 100) if payment_count > 0 else 0

        payment_stats = {
            "payment_count": payment_count,
            "total_amount": total_amount / 100,  # Convert cents to dollars
            "refunds_count": refunds_count,
            "refunds_amount": refunds_amount / 100,  # Convert cents to dollars
            "refund_rate": refund_rate,
            "p50_risk": p50_risk,
            "p90_risk": p90_risk,
            "next_review_threshold": organization.next_review_threshold,
            "total_transfer_sum": total_transfer_sum,
        }

    # Render based on section
    with layout(
        request,
        [
            ("Organizations V2", str(request.url_for("organizations-v2:list"))),
            (organization.name, str(request.url)),
        ],
        "organizations-v2:detail",
    ):
        with detail_view.render(request, section):
            # Render section content
            if section == "overview":
                overview = OverviewSection(organization)
                with overview.render(
                    request, setup_data=setup_data, payment_stats=payment_stats
                ):
                    pass
            elif section == "team":
                # Get admin user for the organization
                admin_user = await repository.get_admin_user(session, organization)
                team_section = TeamSection(organization, admin_user)
                with team_section.render(request):
                    pass
            elif section == "account":
                account_section = AccountSection(organization)
                with account_section.render(request):
                    pass
            elif section == "files":
                # TODO: Fetch files from repository
                files_section = FilesSection(organization, files=[])
                with files_section.render(request):
                    pass
            elif section == "history":
                # TODO: Implement history section
                with tag.div():
                    text("History section coming soon...")
            elif section == "settings":
                settings_section = SettingsSection(organization)
                with settings_section.render(request):
                    pass
            else:
                with tag.div():
                    text(f"Unknown section: {section}")


@router.post("/{organization_id}/approve", name="organizations-v2:approve")
async def approve_organization(
    request: Request,
    organization_id: UUID4,
    threshold: int | None = Query(None),
    session: AsyncSession = Depends(get_db_session),
) -> HXRedirectResponse:
    """Approve an organization with optional threshold."""
    repository = OrganizationRepository(session)

    organization = await repository.get_by_id(organization_id)
    if not organization:
        raise HTTPException(status_code=404, detail="Organization not found")

    # Use provided threshold or default to $250 in cents (max as per requirement)
    next_review_threshold = threshold if threshold else 25000

    # Approve the organization
    await organization_service.confirm_organization_reviewed(
        session, organization, next_review_threshold
    )

    return HXRedirectResponse(
        request,
        str(
            request.url_for("organizations-v2:detail", organization_id=organization_id)
        ),
        303,
    )


@router.api_route(
    "/{organization_id}/deny-dialog",
    name="organizations-v2:deny_dialog",
    methods=["GET", "POST"],
    response_model=None,
)
async def deny_dialog(
    request: Request,
    organization_id: UUID4,
    session: AsyncSession = Depends(get_db_session),
) -> HXRedirectResponse | None:
    """Deny organization dialog and action."""
    repository = OrganizationRepository(session)

    organization = await repository.get_by_id(organization_id)
    if not organization:
        raise HTTPException(status_code=404, detail="Organization not found")

    if request.method == "POST":
        # Deny the organization
        await organization_service.deny_organization(session, organization)

        return HXRedirectResponse(
            request,
            str(
                request.url_for(
                    "organizations-v2:detail", organization_id=organization_id
                )
            ),
            303,
        )

    with modal("Deny Organization", open=True):
        with tag.div(classes="flex flex-col gap-4"):
            with tag.p(classes="font-semibold text-error"):
                text("⚠️ Warning: Payments will be blocked")

            with tag.div(classes="bg-base-200 p-4 rounded-lg"):
                with tag.p(classes="mb-2"):
                    text(
                        "Denying this organization will prevent them from receiving payments. "
                        "This action can be reversed, but the organization will need to be reviewed again."
                    )

            with tag.div(classes="modal-action pt-6 border-t border-base-200"):
                with tag.form(method="dialog"):
                    with button(ghost=True):
                        text("Cancel")
                with button(
                    variant="error",
                    hx_post=str(
                        request.url_for(
                            "organizations-v2:deny_dialog",
                            organization_id=organization_id,
                        )
                    ),
                ):
                    text("Deny Organization")

    return None


@router.api_route(
    "/{organization_id}/approve-denied-dialog",
    name="organizations-v2:approve_denied_dialog",
    methods=["GET", "POST"],
    response_model=None,
)
async def approve_denied_dialog(
    request: Request,
    organization_id: UUID4,
    session: AsyncSession = Depends(get_db_session),
) -> HXRedirectResponse | None:
    """Approve a denied organization dialog and action."""
    repository = OrganizationRepository(session)

    organization = await repository.get_by_id(organization_id)
    if not organization:
        raise HTTPException(status_code=404, detail="Organization not found")

    if request.method == "POST":
        data = await request.form()
        # Convert dollars to cents (user enters 250, we store 25000)
        raw_threshold = data.get("threshold", "250")
        threshold = int(float(str(raw_threshold)) * 100)

        # Approve the organization
        await organization_service.confirm_organization_reviewed(
            session, organization, threshold
        )

        return HXRedirectResponse(
            request,
            str(
                request.url_for(
                    "organizations-v2:detail", organization_id=organization_id
                )
            ),
            303,
        )

    with modal("Approve Denied Organization", open=True):
        with tag.div(classes="flex flex-col gap-4"):
            with tag.p(classes="font-semibold"):
                text("Approve this previously denied organization")

            with tag.div(classes="bg-base-200 p-4 rounded-lg"):
                with tag.p(classes="mb-3"):
                    text(
                        "This will set the organization to ACTIVE status and allow them to receive payments. "
                        "Make sure you've reviewed the organization details and any appeal information."
                    )

                with tag.div(classes="form-control"):
                    with tag.label(classes="label"):
                        with tag.span(classes="label-text font-semibold"):
                            text("Next Review Threshold (in dollars)")
                    with tag.input(
                        type="number",
                        name="threshold",
                        value="250",
                        placeholder="250",
                        classes="input input-bordered",
                    ):
                        pass
                    with tag.label(classes="label"):
                        with tag.span(classes="label-text-alt"):
                            text("Amount in dollars that will trigger next review")

            with tag.div(classes="modal-action pt-6 border-t border-base-200"):
                with tag.form(method="dialog"):
                    with button(ghost=True):
                        text("Cancel")
                with tag.form(
                    hx_post=str(
                        request.url_for(
                            "organizations-v2:approve_denied_dialog",
                            organization_id=organization_id,
                        )
                    ),
                    hx_target="#modal",
                ):
                    with button(variant="primary"):
                        text("Approve Organization")

    return None


@router.api_route(
    "/{organization_id}/unblock-approve-dialog",
    name="organizations-v2:unblock_approve_dialog",
    methods=["GET", "POST"],
    response_model=None,
)
async def unblock_approve_dialog(
    request: Request,
    organization_id: UUID4,
    session: AsyncSession = Depends(get_db_session),
) -> HXRedirectResponse | None:
    """Unblock and approve organization dialog and action."""
    repository = OrganizationRepository(session)

    organization = await repository.get_by_id(organization_id)
    if not organization:
        raise HTTPException(status_code=404, detail="Organization not found")

    if request.method == "POST":
        data = await request.form()
        # Convert dollars to cents (user enters 250, we store 25000)
        raw_threshold = data.get("threshold", "250")
        threshold = int(float(str(raw_threshold)) * 100)

        # Unblock the organization (set blocked_at to None)
        organization.blocked_at = None

        # Approve the organization
        await organization_service.confirm_organization_reviewed(
            session, organization, threshold
        )

        return HXRedirectResponse(
            request,
            str(
                request.url_for(
                    "organizations-v2:detail", organization_id=organization_id
                )
            ),
            303,
        )

    with modal("Unblock & Approve Organization", open=True):
        with tag.div(classes="flex flex-col gap-4"):
            with tag.p(classes="font-semibold"):
                text("Unblock and approve this organization")

            with tag.div(classes="bg-base-200 p-4 rounded-lg"):
                with tag.p(classes="mb-3"):
                    text(
                        "This will unblock the organization and set it to ACTIVE status. "
                        "The organization will be able to receive payments again."
                    )

                with tag.div(classes="form-control"):
                    with tag.label(classes="label"):
                        with tag.span(classes="label-text font-semibold"):
                            text("Next Review Threshold (in dollars)")
                    with tag.input(
                        type="number",
                        name="threshold",
                        value="250",
                        placeholder="250",
                        classes="input input-bordered",
                    ):
                        pass
                    with tag.label(classes="label"):
                        with tag.span(classes="label-text-alt"):
                            text("Amount in dollars that will trigger next review")

            with tag.div(classes="modal-action pt-6 border-t border-base-200"):
                with tag.form(method="dialog"):
                    with button(ghost=True):
                        text("Cancel")
                with tag.form(
                    hx_post=str(
                        request.url_for(
                            "organizations-v2:unblock_approve_dialog",
                            organization_id=organization_id,
                        )
                    ),
                    hx_target="#modal",
                ):
                    with button(variant="primary"):
                        text("Unblock & Approve")

    return None


@router.api_route(
    "/{organization_id}/block-dialog",
    name="organizations-v2:block_dialog",
    methods=["GET", "POST"],
    response_model=None,
)
async def block_dialog(
    request: Request,
    organization_id: UUID4,
    session: AsyncSession = Depends(get_db_session),
) -> HXRedirectResponse | None:
    """Block organization dialog and action."""
    repository = OrganizationRepository(session)

    organization = await repository.get_by_id(organization_id)
    if not organization:
        raise HTTPException(status_code=404, detail="Organization not found")

    if request.method == "POST":
        # Block the organization (set blocked_at to current time)
        from datetime import UTC, datetime

        organization.blocked_at = datetime.now(UTC)

        return HXRedirectResponse(
            request,
            str(
                request.url_for(
                    "organizations-v2:detail", organization_id=organization_id
                )
            ),
            303,
        )

    with modal("Block Organization", open=True):
        with tag.div(classes="flex flex-col gap-4"):
            with tag.p(classes="font-semibold text-error"):
                text("⚠️ Critical Warning: Complete Organization Block")

            with tag.div(classes="bg-error/10 border border-error/20 p-4 rounded-lg"):
                with tag.p(classes="font-semibold mb-2 text-error"):
                    text("Blocking this organization will:")
                with tag.ul(classes="list-disc list-inside space-y-1 text-sm"):
                    with tag.li():
                        text("Prevent all access to the organization")
                    with tag.li():
                        text("Block all payments and transactions")
                    with tag.li():
                        text("Disable API access")
                    with tag.li():
                        text("Prevent any organization operations")

                with tag.p(classes="mt-3 text-sm font-semibold"):
                    text(
                        "This is a severe action typically used for fraud or ToS violations."
                    )

            with tag.div(classes="modal-action pt-6 border-t border-base-200"):
                with tag.form(method="dialog"):
                    with button(ghost=True):
                        text("Cancel")
                with button(
                    variant="error",
                    hx_post=str(
                        request.url_for(
                            "organizations-v2:block_dialog",
                            organization_id=organization_id,
                        )
                    ),
                ):
                    text("Block Organization")

    return None


@router.api_route(
    "/{organization_id}/edit",
    name="organizations-v2:edit",
    methods=["GET", "POST"],
    response_model=None,
)
async def edit_organization(
    request: Request,
    organization_id: UUID4,
    session: AsyncSession = Depends(get_db_session),
) -> HXRedirectResponse | None:
    """Edit organization details."""
    repository = OrganizationRepository(session)

    # Fetch organization
    organization = await repository.get_by_id(organization_id)
    if not organization:
        raise HTTPException(status_code=404, detail="Organization not found")

    validation_error = None

    if request.method == "POST":
        data = await request.form()
        try:
            form = UpdateOrganizationBasicForm.model_validate_form(data)
            if form.slug != organization.slug:
                existing_slug = await repository.get_by_slug(form.slug)
                if existing_slug is not None:
                    raise ValidationError.from_exception_data(
                        title="SlugAlreadyExists",
                        line_errors=[
                            {
                                "loc": ("slug",),
                                "type": PydanticCustomError(
                                    "SlugAlreadyExists",
                                    "An organization with this slug already exists.",
                                ),
                                "input": form.slug,
                            }
                        ],
                    )

            # Update organization with basic fields only
            form_dict = form.model_dump(exclude_none=True)
            organization = await repository.update(
                organization,
                update_dict=form_dict,
            )
            redirect_url = (
                str(
                    request.url_for(
                        "organizations-v2:detail", organization_id=organization_id
                    )
                )
