"""Projects API views.

Endpoints
---------
GET  /projects/                               list projects
POST /projects/                               create project
GET  /projects/{public_id}/                   retrieve project (detail)
PATCH /projects/{public_id}/                  update project
DELETE /projects/{public_id}/                 delete project
POST /projects/{public_id}/change-status/     change project status
POST /projects/{public_id}/members/           add member
DELETE /projects/{public_id}/members/{user_id}/ remove member
GET  /projects/{public_id}/phases/            list phases
POST /projects/{public_id}/phases/            create phase
PATCH /projects/{public_id}/phases/{phase_id}/  update phase
DELETE /projects/{public_id}/phases/{phase_id}/ delete phase
GET  /projects/{public_id}/milestones/        list milestones
POST /projects/{public_id}/milestones/        create milestone
PATCH /projects/{public_id}/milestones/{milestone_id}/  update milestone
DELETE /projects/{public_id}/milestones/{milestone_id}/ delete milestone
POST /projects/{public_id}/milestones/{milestone_id}/complete/ complete milestone
GET  /projects/tags/                          list tags
POST /projects/tags/                          create tag
PATCH /projects/tags/{tag_id}/                update tag
DELETE /projects/tags/{tag_id}/               delete tag
"""

from __future__ import annotations

from django.shortcuts import get_object_or_404
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response

from simorgh.apps.projects import selectors as sel
from simorgh.apps.projects import services
from simorgh.apps.projects.models import (
    Milestone,
    Project,
    ProjectMember,
    ProjectPhase,
    ProjectTag,
)


# ---------------------------------------------------------------------------
# Serialization helpers
# ---------------------------------------------------------------------------

def _serialize_tag(tag: ProjectTag) -> dict:
    return {
        "id": tag.pk,
        "public_id": str(tag.public_id),
        "name": tag.name,
        "color": tag.color,
    }


def _serialize_member(m: ProjectMember) -> dict:
    user = m.user
    return {
        "id": m.pk,
        "user_id": user.pk,
        "username": getattr(user, "username", ""),
        "full_name": getattr(user, "get_full_name", lambda: "")() or getattr(user, "username", ""),
        "role": m.role,
        "joined_at": m.joined_at.isoformat(),
    }


def _serialize_phase(phase: ProjectPhase) -> dict:
    return {
        "id": phase.pk,
        "public_id": str(phase.public_id),
        "name": phase.name,
        "order": phase.order,
        "status": phase.status,
        "start_date": phase.start_date.isoformat() if phase.start_date else None,
        "end_date": phase.end_date.isoformat() if phase.end_date else None,
        "description": phase.description,
    }


def _serialize_milestone(ms: Milestone) -> dict:
    return {
        "id": ms.pk,
        "public_id": str(ms.public_id),
        "name": ms.name,
        "due_date": ms.due_date.isoformat(),
        "is_completed": ms.is_completed,
        "completed_at": ms.completed_at.isoformat() if ms.completed_at else None,
        "description": ms.description,
    }


def _serialize_project(project: Project, *, detail: bool = False) -> dict:
    data: dict = {
        "id": project.pk,
        "public_id": str(project.public_id),
        "name": project.name,
        "code": project.code,
        "status": project.status,
        "start_date": project.start_date.isoformat() if project.start_date else None,
        "end_date": project.end_date.isoformat() if project.end_date else None,
        "owner_id": project.owner_id,
        "color": project.color,
        "is_billable": project.is_billable,
        "budget": str(project.budget) if project.budget is not None else None,
        "contact_id": str(project.contact_id) if project.contact_id else None,
        "tags": [_serialize_tag(t) for t in project.tags.all()],
        "created_at": project.created_at.isoformat(),
        "updated_at": project.updated_at.isoformat(),
        "milestone_total": project.milestones.count(),
        "milestone_completed": project.milestones.filter(is_completed=True).count(),
    }
    if detail:
        data["description"] = project.description
        data["members"] = [_serialize_member(m) for m in project.memberships.select_related("user").all()]
        data["phases"] = [_serialize_phase(ph) for ph in project.phases.order_by("order").all()]
        data["milestones"] = [_serialize_milestone(ms) for ms in project.milestones.order_by("due_date").all()]
    return data


def _get_tenant_id(request: Request) -> int | None:
    tenant = getattr(request, "tenant", None)
    return tenant.pk if tenant else None


def _get_org_node_id(request: Request) -> int | None:
    tenant = getattr(request, "tenant", None)
    if tenant is None:
        return None
    # Use the user's primary org node or fallback to root
    try:
        from simorgh.apps.organizations.models import OrganizationNode
        node = OrganizationNode.objects.filter(tenant=tenant).order_by("lft").first()
        return node.pk if node else None
    except Exception:
        return None


# ---------------------------------------------------------------------------
# Project list / create
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def project_list_create(request: Request) -> Response:
    """GET /projects/ — POST /projects/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    if request.method == "GET":
        qs = sel.projects_for_tenant(tenant_id)

        # Filters
        status_filter = request.query_params.get("status")
        if status_filter:
            qs = qs.filter(status=status_filter)

        owner_filter = request.query_params.get("owner")
        if owner_filter:
            qs = qs.filter(owner_id=owner_filter)

        member_filter = request.query_params.get("member")
        if member_filter:
            qs = qs.filter(memberships__user_id=member_filter).distinct()

        tag_ids_raw = request.query_params.getlist("tag")
        if tag_ids_raw:
            for tid in tag_ids_raw:
                if tid.isdigit():
                    qs = qs.filter(tags__id=tid)
            qs = qs.distinct()

        qs = qs.prefetch_related("tags").select_related("owner").order_by("-created_at")
        return Response([_serialize_project(p) for p in qs])

    # POST
    data = request.data
    org_node_id = data.get("organization_node_id") or _get_org_node_id(request)
    if not org_node_id:
        return Response({"detail": "organization_node_id required."}, status=status.HTTP_400_BAD_REQUEST)

    try:
        project = services.create_project(
            tenant_id=tenant_id,
            organization_node_id=int(org_node_id),
            name=data["name"],
            owner_id=data.get("owner_id") or request.user.pk,
            code=data.get("code", ""),
            description=data.get("description", ""),
            status=data.get("status", "draft"),
            start_date=data.get("start_date") or None,
            end_date=data.get("end_date") or None,
            color=data.get("color", "#4f46e5"),
            is_billable=data.get("is_billable", False),
            budget=data.get("budget") or None,
            contact_id=data.get("contact_id") or None,
            tag_ids=[int(t) for t in data.get("tag_ids", [])],
        )
    except KeyError as exc:
        return Response({"detail": f"Missing field: {exc}"}, status=status.HTTP_400_BAD_REQUEST)
    except ValueError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    project = Project.objects.prefetch_related(
        "tags", "memberships__user", "phases", "milestones"
    ).get(pk=project.pk)
    return Response(_serialize_project(project, detail=True), status=status.HTTP_201_CREATED)


# ---------------------------------------------------------------------------
# Project retrieve / update / delete
# ---------------------------------------------------------------------------

@api_view(["GET", "PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def project_detail(request: Request, public_id: str) -> Response:
    """GET/PATCH/DELETE /projects/{public_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    project = get_object_or_404(
        Project.objects.prefetch_related("tags", "memberships__user", "phases", "milestones"),
        tenant_id=tenant_id,
        public_id=public_id,
    )

    if request.method == "GET":
        return Response(_serialize_project(project, detail=True))

    if request.method == "DELETE":
        services.delete_project(project)
        return Response(status=status.HTTP_204_NO_CONTENT)

    # PATCH
    data = request.data
    try:
        _SENTINEL = object()
        kwargs: dict = {}
        if "name" in data:
            kwargs["name"] = data["name"]
        if "code" in data:
            kwargs["code"] = data["code"]
        if "description" in data:
            kwargs["description"] = data["description"]
        if "status" in data:
            kwargs["status"] = data["status"]
        if "start_date" in data:
            kwargs["start_date"] = data["start_date"] or None
        if "end_date" in data:
            kwargs["end_date"] = data["end_date"] or None
        if "color" in data:
            kwargs["color"] = data["color"]
        if "is_billable" in data:
            kwargs["is_billable"] = data["is_billable"]
        if "budget" in data:
            kwargs["budget"] = data["budget"] or None
        if "contact_id" in data:
            kwargs["contact_id"] = data["contact_id"] or None
        if "tag_ids" in data:
            kwargs["tag_ids"] = [int(t) for t in data["tag_ids"]]
        kwargs["actor_id"] = request.user.pk

        project = services.update_project(project, **kwargs)
    except ValueError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    project = Project.objects.prefetch_related(
        "tags", "memberships__user", "phases", "milestones"
    ).get(pk=project.pk)
    return Response(_serialize_project(project, detail=True))


# ---------------------------------------------------------------------------
# Change project status
# ---------------------------------------------------------------------------

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def project_change_status(request: Request, public_id: str) -> Response:
    """POST /projects/{public_id}/change-status/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    project = get_object_or_404(Project, tenant_id=tenant_id, public_id=public_id)
    new_status = request.data.get("status")
    if not new_status:
        return Response({"detail": "status is required."}, status=status.HTTP_400_BAD_REQUEST)

    from simorgh.apps.projects.models import ProjectStatus
    valid = [c[0] for c in ProjectStatus.choices]
    if new_status not in valid:
        return Response({"detail": f"Invalid status. Choices: {valid}"}, status=status.HTTP_400_BAD_REQUEST)

    project = services.change_project_status(project, new_status=new_status, actor_id=request.user.pk)
    return Response({"status": project.status})


# ---------------------------------------------------------------------------
# Members
# ---------------------------------------------------------------------------

@api_view(["POST"])
@permission_classes([IsAuthenticated])
def project_add_member(request: Request, public_id: str) -> Response:
    """POST /projects/{public_id}/members/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    project = get_object_or_404(Project, tenant_id=tenant_id, public_id=public_id)
    user_id = request.data.get("user_id")
    if not user_id:
        return Response({"detail": "user_id is required."}, status=status.HTTP_400_BAD_REQUEST)

    role = request.data.get("role", "member")
    member = services.add_project_member(project, user_id=int(user_id), role=role, actor_id=request.user.pk)
    return Response(_serialize_member(member), status=status.HTTP_201_CREATED)


@api_view(["DELETE"])
@permission_classes([IsAuthenticated])
def project_remove_member(request: Request, public_id: str, user_id: int) -> Response:
    """DELETE /projects/{public_id}/members/{user_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    project = get_object_or_404(Project, tenant_id=tenant_id, public_id=public_id)
    services.remove_project_member(project, user_id=user_id, actor_id=request.user.pk)
    return Response(status=status.HTTP_204_NO_CONTENT)


# ---------------------------------------------------------------------------
# Phases
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def project_phases(request: Request, public_id: str) -> Response:
    """GET/POST /projects/{public_id}/phases/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    project = get_object_or_404(Project, tenant_id=tenant_id, public_id=public_id)

    if request.method == "GET":
        phases = sel.phases_for_project(project.pk)
        return Response([_serialize_phase(ph) for ph in phases])

    data = request.data
    try:
        phase = services.create_phase(
            project,
            name=data["name"],
            order=int(data.get("order", 0)),
            start_date=data.get("start_date") or None,
            end_date=data.get("end_date") or None,
            status=data.get("status", "planned"),
            description=data.get("description", ""),
        )
    except KeyError as exc:
        return Response({"detail": f"Missing field: {exc}"}, status=status.HTTP_400_BAD_REQUEST)

    return Response(_serialize_phase(phase), status=status.HTTP_201_CREATED)


@api_view(["PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def project_phase_detail(request: Request, public_id: str, phase_id: int) -> Response:
    """PATCH/DELETE /projects/{public_id}/phases/{phase_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    project = get_object_or_404(Project, tenant_id=tenant_id, public_id=public_id)
    phase = get_object_or_404(ProjectPhase, project=project, pk=phase_id)

    if request.method == "DELETE":
        services.delete_phase(phase)
        return Response(status=status.HTTP_204_NO_CONTENT)

    data = request.data
    kwargs: dict = {}
    if "name" in data:
        kwargs["name"] = data["name"]
    if "order" in data:
        kwargs["order"] = int(data["order"])
    if "start_date" in data:
        kwargs["start_date"] = data["start_date"] or None
    if "end_date" in data:
        kwargs["end_date"] = data["end_date"] or None
    if "status" in data:
        kwargs["status"] = data["status"]
    if "description" in data:
        kwargs["description"] = data["description"]

    phase = services.update_phase(phase, **kwargs)
    return Response(_serialize_phase(phase))


# ---------------------------------------------------------------------------
# Milestones
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def project_milestones(request: Request, public_id: str) -> Response:
    """GET/POST /projects/{public_id}/milestones/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    project = get_object_or_404(Project, tenant_id=tenant_id, public_id=public_id)

    if request.method == "GET":
        milestones = sel.milestones_for_project(project.pk)
        return Response([_serialize_milestone(ms) for ms in milestones])

    data = request.data
    try:
        from datetime import date as _date
        due_date_raw = data["due_date"]
        due_date = _date.fromisoformat(due_date_raw) if isinstance(due_date_raw, str) else due_date_raw
        milestone = services.create_milestone(
            project,
            name=data["name"],
            due_date=due_date,
            description=data.get("description", ""),
        )
    except KeyError as exc:
        return Response({"detail": f"Missing field: {exc}"}, status=status.HTTP_400_BAD_REQUEST)
    except ValueError as exc:
        return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)

    return Response(_serialize_milestone(milestone), status=status.HTTP_201_CREATED)


@api_view(["PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def project_milestone_detail(request: Request, public_id: str, milestone_id: int) -> Response:
    """PATCH/DELETE /projects/{public_id}/milestones/{milestone_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    project = get_object_or_404(Project, tenant_id=tenant_id, public_id=public_id)
    milestone = get_object_or_404(Milestone, project=project, pk=milestone_id)

    if request.method == "DELETE":
        services.delete_milestone(milestone)
        return Response(status=status.HTTP_204_NO_CONTENT)

    data = request.data
    kwargs: dict = {}
    if "name" in data:
        kwargs["name"] = data["name"]
    if "due_date" in data:
        kwargs["due_date"] = data["due_date"]
    if "description" in data:
        kwargs["description"] = data["description"]

    milestone = services.update_milestone(milestone, **kwargs)
    return Response(_serialize_milestone(milestone))


@api_view(["POST"])
@permission_classes([IsAuthenticated])
def project_milestone_complete(request: Request, public_id: str, milestone_id: int) -> Response:
    """POST /projects/{public_id}/milestones/{milestone_id}/complete/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    project = get_object_or_404(Project, tenant_id=tenant_id, public_id=public_id)
    milestone = get_object_or_404(Milestone, project=project, pk=milestone_id)
    milestone = services.complete_milestone(milestone, actor_id=request.user.pk)
    return Response(_serialize_milestone(milestone))


# ---------------------------------------------------------------------------
# Tags
# ---------------------------------------------------------------------------

@api_view(["GET", "POST"])
@permission_classes([IsAuthenticated])
def tag_list_create(request: Request) -> Response:
    """GET/POST /projects/tags/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    if request.method == "GET":
        tags = sel.tags_for_tenant(tenant_id)
        return Response([_serialize_tag(t) for t in tags])

    data = request.data
    try:
        tag = services.create_project_tag(
            tenant_id=tenant_id,
            name=data["name"],
            color=data.get("color", ""),
        )
    except KeyError as exc:
        return Response({"detail": f"Missing field: {exc}"}, status=status.HTTP_400_BAD_REQUEST)

    return Response(_serialize_tag(tag), status=status.HTTP_201_CREATED)


@api_view(["PATCH", "DELETE"])
@permission_classes([IsAuthenticated])
def tag_detail(request: Request, tag_id: int) -> Response:
    """PATCH/DELETE /projects/tags/{tag_id}/"""
    tenant_id = _get_tenant_id(request)
    if not tenant_id:
        return Response({"detail": "Tenant header required."}, status=status.HTTP_400_BAD_REQUEST)

    tag = get_object_or_404(ProjectTag, tenant_id=tenant_id, pk=tag_id)

    if request.method == "DELETE":
        services.delete_project_tag(tag)
        return Response(status=status.HTTP_204_NO_CONTENT)

    data = request.data
    tag = services.update_project_tag(tag, name=data.get("name"), color=data.get("color"))
    return Response(_serialize_tag(tag))
