"""Organization-level task custom field definition management."""

from __future__ import annotations

from typing import TYPE_CHECKING
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import func
from sqlalchemy.exc import IntegrityError
from sqlmodel import col, select

from app.api.deps import require_org_admin, require_org_member
from app.core.time import utcnow
from app.db.session import get_session
from app.models.boards import Board
from app.models.task_custom_fields import (
    BoardTaskCustomField,
    TaskCustomFieldDefinition,
    TaskCustomFieldValue,
)
from app.schemas.common import OkResponse
from app.schemas.task_custom_fields import (
    TaskCustomFieldDefinitionCreate,
    TaskCustomFieldDefinitionRead,
    TaskCustomFieldDefinitionUpdate,
    validate_custom_field_definition,
)
from app.services.organizations import OrganizationContext

if TYPE_CHECKING:
    from sqlmodel.ext.asyncio.session import AsyncSession


router = APIRouter(prefix="/organizations/me/custom-fields", tags=["custom-fields"])
SESSION_DEP = Depends(get_session)
ORG_MEMBER_DEP = Depends(require_org_member)
ORG_ADMIN_DEP = Depends(require_org_admin)


def _to_definition_read_payload(
    *,
    definition: TaskCustomFieldDefinition,
    board_ids: list[UUID],
) -> TaskCustomFieldDefinitionRead:
    payload = TaskCustomFieldDefinitionRead.model_validate(definition, from_attributes=True)
    payload.board_ids = board_ids
    return payload


async def _board_ids_by_definition_id(
    *,
    session: AsyncSession,
    definition_ids: list[UUID],
) -> dict[UUID, list[UUID]]:
    if not definition_ids:
        return {}
    rows = (
        await session.exec(
            select(
                col(BoardTaskCustomField.task_custom_field_definition_id),
                col(BoardTaskCustomField.board_id),
            ).where(
                col(BoardTaskCustomField.task_custom_field_definition_id).in_(definition_ids),
            ),
        )
    ).all()
    board_ids_by_definition_id: dict[UUID, list[UUID]] = {
        definition_id: [] for definition_id in definition_ids
    }
    for definition_id, board_id in rows:
        board_ids_by_definition_id.setdefault(definition_id, []).append(board_id)
    for definition_id in board_ids_by_definition_id:
        board_ids_by_definition_id[definition_id].sort(key=str)
    return board_ids_by_definition_id


async def _validated_board_ids_for_org(
    *,
    session: AsyncSession,
    ctx: OrganizationContext,
    board_ids: list[UUID],
) -> list[UUID]:
    normalized_board_ids = list(dict.fromkeys(board_ids))
    if not normalized_board_ids:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail="At least one board must be selected.",
        )
    valid_board_ids = set(
        (
            await session.exec(
                select(col(Board.id)).where(
                    col(Board.organization_id) == ctx.organization.id,
                    col(Board.id).in_(normalized_board_ids),
                ),
            )
        ).all(),
    )
    missing_board_ids = sorted(
        {board_id for board_id in normalized_board_ids if board_id not in valid_board_ids},
        key=str,
    )
    if missing_board_ids:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail={
                "message": "Some selected boards are invalid for this organization.",
                "invalid_board_ids": [str(value) for value in missing_board_ids],
            },
        )
    return normalized_board_ids


async def _get_org_definition(
    *,
    session: AsyncSession,
    ctx: OrganizationContext,
    definition_id: UUID,
) -> TaskCustomFieldDefinition:
    definition = (
        await session.exec(
            select(TaskCustomFieldDefinition).where(
                col(TaskCustomFieldDefinition.id) == definition_id,
                col(TaskCustomFieldDefinition.organization_id) == ctx.organization.id,
            ),
        )
    ).first()
    if definition is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
    return definition


@router.get("", response_model=list[TaskCustomFieldDefinitionRead])
async def list_org_custom_fields(
    ctx: OrganizationContext = ORG_MEMBER_DEP,
    session: AsyncSession = SESSION_DEP,
) -> list[TaskCustomFieldDefinitionRead]:
    """List task custom field definitions for the authenticated organization."""
    definitions = list(
        await session.exec(
            select(TaskCustomFieldDefinition)
            .where(col(TaskCustomFieldDefinition.organization_id) == ctx.organization.id)
            .order_by(func.lower(col(TaskCustomFieldDefinition.label)).asc()),
        ),
    )
    board_ids_by_definition_id = await _board_ids_by_definition_id(
        session=session,
        definition_ids=[definition.id for definition in definitions],
    )
    return [
        _to_definition_read_payload(
            definition=definition,
            board_ids=board_ids_by_definition_id.get(definition.id, []),
        )
        for definition in definitions
    ]


@router.post("", response_model=TaskCustomFieldDefinitionRead)
async def create_org_custom_field(
    payload: TaskCustomFieldDefinitionCreate,
    ctx: OrganizationContext = ORG_ADMIN_DEP,
    session: AsyncSession = SESSION_DEP,
) -> TaskCustomFieldDefinitionRead:
    """Create an organization-level task custom field definition."""
    board_ids = await _validated_board_ids_for_org(
        session=session,
        ctx=ctx,
        board_ids=payload.board_ids,
    )
    try:
        validate_custom_field_definition(
            field_type=payload.field_type,
            validation_regex=payload.validation_regex,
            default_value=payload.default_value,
        )
    except ValueError as err:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=str(err),
        ) from err
    definition = TaskCustomFieldDefinition(
        organization_id=ctx.organization.id,
        field_key=payload.field_key,
        label=payload.label or payload.field_key,
        field_type=payload.field_type,
        ui_visibility=payload.ui_visibility,
        validation_regex=payload.validation_regex,
        description=payload.description,
        required=payload.required,
        default_value=payload.default_value,
    )
    session.add(definition)
    await session.flush()
    for board_id in board_ids:
        session.add(
            BoardTaskCustomField(
                board_id=board_id,
                task_custom_field_definition_id=definition.id,
            ),
        )
    try:
        await session.commit()
    except IntegrityError as err:
        await session.rollback()
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Field key already exists in this organization.",
        ) from err

    await session.refresh(definition)
    return _to_definition_read_payload(definition=definition, board_ids=board_ids)


@router.patch("/{task_custom_field_definition_id}", response_model=TaskCustomFieldDefinitionRead)
async def update_org_custom_field(
    task_custom_field_definition_id: UUID,
    payload: TaskCustomFieldDefinitionUpdate,
    ctx: OrganizationContext = ORG_ADMIN_DEP,
    session: AsyncSession = SESSION_DEP,
) -> TaskCustomFieldDefinitionRead:
    """Update an organization-level task custom field definition."""
    definition = await _get_org_definition(
        session=session,
        ctx=ctx,
        definition_id=task_custom_field_definition_id,
    )
    updates = payload.model_dump(exclude_unset=True)
    board_ids = updates.pop("board_ids", None)
    validated_board_ids: list[UUID] | None = None
    if board_ids is not None:
        validated_board_ids = await _validated_board_ids_for_org(
            session=session,
            ctx=ctx,
            board_ids=board_ids,
        )
    next_field_type = updates.get("field_type", definition.field_type)
    next_validation_regex = (
        updates["validation_regex"]
        if "validation_regex" in updates
        else definition.validation_regex
    )
    next_default_value = (
        updates["default_value"] if "default_value" in updates else definition.default_value
    )
    try:
        validate_custom_field_definition(
            field_type=next_field_type,
            validation_regex=next_validation_regex,
            default_value=next_default_value,
        )
    except ValueError as err:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
            detail=str(err),
        ) from err
    for key, value in updates.items():
        setattr(definition, key, value)
    if validated_board_ids is not None:
        bindings = list(
            await session.exec(
                select(BoardTaskCustomField).where(
                    col(BoardTaskCustomField.task_custom_field_definition_id) == definition.id,
                ),
            ),
        )
        current_board_ids = {binding.board_id for binding in bindings}
        target_board_ids = set(validated_board_ids)
        for binding in bindings:
            if binding.board_id not in target_board_ids:
                await session.delete(binding)
        for board_id in validated_board_ids:
            if board_id in current_board_ids:
                continue
            session.add(
                BoardTaskCustomField(
                    board_id=board_id,
                    task_custom_field_definition_id=definition.id,
                ),
            )
    definition.updated_at = utcnow()
    session.add(definition)

    try:
        await session.commit()
    except IntegrityError as err:
        await session.rollback()
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Field key already exists in this organization.",
        ) from err

    await session.refresh(definition)
    if validated_board_ids is None:
        board_ids = (
            await _board_ids_by_definition_id(
                session=session,
                definition_ids=[definition.id],
            )
        ).get(definition.id, [])
    else:
        board_ids = validated_board_ids
    return _to_definition_read_payload(definition=definition, board_ids=board_ids)


@router.delete("/{task_custom_field_definition_id}", response_model=OkResponse)
async def delete_org_custom_field(
    task_custom_field_definition_id: UUID,
    ctx: OrganizationContext = ORG_ADMIN_DEP,
    session: AsyncSession = SESSION_DEP,
) -> OkResponse:
    """Delete an org-level definition when it has no persisted task values."""
    definition = await _get_org_definition(
        session=session,
        ctx=ctx,
        definition_id=task_custom_field_definition_id,
    )
    value_ids = (
        await session.exec(
            select(col(TaskCustomFieldValue.id)).where(
                col(TaskCustomFieldValue.task_custom_field_definition_id) == definition.id,
            ),
        )
    ).all()
    if value_ids:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Cannot delete a custom field definition while task values exist.",
        )

    bindings = list(
        await session.exec(
            select(BoardTaskCustomField).where(
                col(BoardTaskCustomField.task_custom_field_definition_id) == definition.id,
            ),
        ),
    )
    for binding in bindings:
        await session.delete(binding)
    await session.delete(definition)
    await session.commit()
    return OkResponse()
