# Copyright (c) 2023-present Plane Software, Inc. and contributors
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.

# Python imports
import json

# Django imports
from django.db.models import (
    Case,
    Count,
    F,
    Q,
    Sum,
    FloatField,
    Value,
    When,
)
from django.db import models
from django.db.models.functions import Cast, Concat
from django.utils import timezone

# Module imports
from plane.db.models import (
    Cycle,
    CycleIssue,
    Issue,
    Project,
)
from plane.utils.analytics_plot import burndown_plot
from plane.bgtasks.issue_activities_task import issue_activity
from plane.utils.host import base_host


def transfer_cycle_issues(
    slug,
    project_id,
    cycle_id,
    new_cycle_id,
    request,
    user_id,
):
    """
    Transfer incomplete issues from one cycle to another and create progress snapshot.

    Args:
        slug: Workspace slug
        project_id: Project ID
        cycle_id: Source cycle ID
        new_cycle_id: Destination cycle ID
        request: HTTP request object
        user_id: User ID performing the transfer

    Returns:
        dict: Response data with success or error message
    """
    # Get the new cycle
    new_cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=new_cycle_id).first()

    # Check if new cycle is already completed
    if new_cycle.end_date is not None and new_cycle.end_date < timezone.now():
        return {
            "success": False,
            "error": "The cycle where the issues are transferred is already completed",
        }

    # Get the old cycle with issue counts
    old_cycle = (
        Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=cycle_id)
        .annotate(
            total_issues=Count(
                "issue_cycle",
                filter=Q(
                    issue_cycle__issue__archived_at__isnull=True,
                    issue_cycle__issue__is_draft=False,
                    issue_cycle__deleted_at__isnull=True,
                    issue_cycle__issue__deleted_at__isnull=True,
                ),
            )
        )
        .annotate(
            completed_issues=Count(
                "issue_cycle__issue__state__group",
                filter=Q(
                    issue_cycle__issue__state__group="completed",
                    issue_cycle__issue__archived_at__isnull=True,
                    issue_cycle__issue__is_draft=False,
                    issue_cycle__issue__deleted_at__isnull=True,
                    issue_cycle__deleted_at__isnull=True,
                ),
            )
        )
        .annotate(
            cancelled_issues=Count(
                "issue_cycle__issue__state__group",
                filter=Q(
                    issue_cycle__issue__state__group="cancelled",
                    issue_cycle__issue__archived_at__isnull=True,
                    issue_cycle__issue__is_draft=False,
                    issue_cycle__issue__deleted_at__isnull=True,
                    issue_cycle__deleted_at__isnull=True,
                ),
            )
        )
        .annotate(
            started_issues=Count(
                "issue_cycle__issue__state__group",
                filter=Q(
                    issue_cycle__issue__state__group="started",
                    issue_cycle__issue__archived_at__isnull=True,
                    issue_cycle__issue__is_draft=False,
                    issue_cycle__issue__deleted_at__isnull=True,
                    issue_cycle__deleted_at__isnull=True,
                ),
            )
        )
        .annotate(
            unstarted_issues=Count(
                "issue_cycle__issue__state__group",
                filter=Q(
                    issue_cycle__issue__state__group="unstarted",
                    issue_cycle__issue__archived_at__isnull=True,
                    issue_cycle__issue__is_draft=False,
                    issue_cycle__issue__deleted_at__isnull=True,
                    issue_cycle__deleted_at__isnull=True,
                ),
            )
        )
        .annotate(
            backlog_issues=Count(
                "issue_cycle__issue__state__group",
                filter=Q(
                    issue_cycle__issue__state__group="backlog",
                    issue_cycle__issue__archived_at__isnull=True,
                    issue_cycle__issue__is_draft=False,
                    issue_cycle__issue__deleted_at__isnull=True,
                    issue_cycle__deleted_at__isnull=True,
                ),
            )
        )
    )
    old_cycle = old_cycle.first()

    if old_cycle is None:
        return {
            "success": False,
            "error": "Source cycle not found",
        }

    # Check if project uses estimates
    estimate_type = Project.objects.filter(
        workspace__slug=slug,
        pk=project_id,
        estimate__isnull=False,
        estimate__type="points",
    ).exists()

    # Initialize estimate distribution variables
    assignee_estimate_distribution = []
    label_estimate_distribution = []
    estimate_completion_chart = {}

    if estimate_type:
        assignee_estimate_data = (
            Issue.issue_objects.filter(
                issue_cycle__cycle_id=cycle_id,
                issue_cycle__deleted_at__isnull=True,
                workspace__slug=slug,
                project_id=project_id,
            )
            .annotate(display_name=F("assignees__display_name"))
            .annotate(assignee_id=F("assignees__id"))
            .annotate(
                avatar_url=Case(
                    # If `avatar_asset` exists, use it to generate the asset URL
                    When(
                        assignees__avatar_asset__isnull=False,
                        then=Concat(
                            Value("/api/assets/v2/static/"),
                            Cast("assignees__avatar_asset", models.CharField()),
                            Value("/"),
                        ),
                    ),
                    # If `avatar_asset` is None, fall back to using `avatar` field directly
                    When(
                        assignees__avatar_asset__isnull=True,
                        then="assignees__avatar",
                    ),
                    default=Value(None),
                    output_field=models.CharField(),
                )
            )
            .values("display_name", "assignee_id", "avatar_url")
            .annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
            .annotate(
                completed_estimates=Sum(
                    Cast("estimate_point__value", FloatField()),
                    filter=Q(
                        completed_at__isnull=False,
                        archived_at__isnull=True,
                        is_draft=False,
                    ),
                )
            )
            .annotate(
                pending_estimates=Sum(
                    Cast("estimate_point__value", FloatField()),
                    filter=Q(
                        completed_at__isnull=True,
                        archived_at__isnull=True,
                        is_draft=False,
                    ),
                )
            )
            .order_by("display_name")
        )
        # Assignee estimate distribution serialization
        assignee_estimate_distribution = [
            {
                "display_name": item["display_name"],
                "assignee_id": (str(item["assignee_id"]) if item["assignee_id"] else None),
                "avatar_url": item.get("avatar_url"),
                "total_estimates": item["total_estimates"],
                "completed_estimates": item["completed_estimates"],
                "pending_estimates": item["pending_estimates"],
            }
            for item in assignee_estimate_data
        ]

        label_distribution_data = (
            Issue.issue_objects.filter(
                issue_cycle__cycle_id=cycle_id,
                issue_cycle__deleted_at__isnull=True,
                workspace__slug=slug,
                project_id=project_id,
            )
            .annotate(label_name=F("labels__name"))
            .annotate(color=F("labels__color"))
            .annotate(label_id=F("labels__id"))
            .values("label_name", "color", "label_id")
            .annotate(total_estimates=Sum(Cast("estimate_point__value", FloatField())))
            .annotate(
                completed_estimates=Sum(
                    Cast("estimate_point__value", FloatField()),
                    filter=Q(
                        completed_at__isnull=False,
                        archived_at__isnull=True,
                        is_draft=False,
                    ),
                )
            )
            .annotate(
                pending_estimates=Sum(
                    Cast("estimate_point__value", FloatField()),
                    filter=Q(
                        completed_at__isnull=True,
                        archived_at__isnull=True,
                        is_draft=False,
                    ),
                )
            )
            .order_by("label_name")
        )

        estimate_completion_chart = burndown_plot(
            queryset=old_cycle,
            slug=slug,
            project_id=project_id,
            plot_type="points",
            cycle_id=cycle_id,
        )
        # Label estimate distribution serialization
        label_estimate_distribution = [
            {
                "label_name": item["label_name"],
                "color": item["color"],
                "label_id": (str(item["label_id"]) if item["label_id"] else None),
                "total_estimates": item["total_estimates"],
                "completed_estimates": item["completed_estimates"],
                "pending_estimates": item["pending_estimates"],
            }
            for item in label_distribution_data
        ]

    # Get the assignee distribution
    assignee_distribution = (
        Issue.issue_objects.filter(
            issue_cycle__cycle_id=cycle_id,
            issue_cycle__deleted_at__isnull=True,
            workspace__slug=slug,
            project_id=project_id,
        )
        .annotate(display_name=F("assignees__display_name"))
        .annotate(assignee_id=F("assignees__id"))
        .annotate(
            avatar_url=Case(
                # If `avatar_asset` exists, use it to generate the asset URL
                When(
                    assignees__avatar_asset__isnull=False,
                    then=Concat(
                        Value("/api/assets/v2/static/"),
                        Cast("assignees__avatar_asset", models.CharField()),
                        Value("/"),
                    ),
                ),
                # If `avatar_asset` is None, fall back to using `avatar` field directly
                When(assignees__avatar_asset__isnull=True, then="assignees__avatar"),
                default=Value(None),
                output_field=models.CharField(),
            )
        )
        .values("display_name", "assignee_id", "avatar_url")
        .annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
        .annotate(
            completed_issues=Count(
                "id",
                filter=Q(
                    completed_at__isnull=False,
                    archived_at__isnull=True,
                    is_draft=False,
                ),
            )
        )
        .annotate(
            pending_issues=Count(
                "id",
                filter=Q(
                    completed_at__isnull=True,
                    archived_at__isnull=True,
                    is_draft=False,
                ),
            )
        )
        .order_by("display_name")
    )
    # Assignee distribution serialized
    assignee_distribution_data = [
        {
            "display_name": item["display_name"],
            "assignee_id": (str(item["assignee_id"]) if item["assignee_id"] else None),
            "avatar_url": item.get("avatar_url"),
            "total_issues": item["total_issues"],
            "completed_issues": item["completed_issues"],
            "pending_issues": item["pending_issues"],
        }
        for item in assignee_distribution
    ]

    # Get the label distribution
    label_distribution = (
        Issue.issue_objects.filter(
            issue_cycle__cycle_id=cycle_id,
            issue_cycle__deleted_at__isnull=True,
            workspace__slug=slug,
            project_id=project_id,
        )
        .annotate(label_name=F("labels__name"))
        .annotate(color=F("labels__color"))
        .annotate(label_id=F("labels__id"))
        .values("label_name", "color", "label_id")
        .annotate(total_issues=Count("id", filter=Q(archived_at__isnull=True, is_draft=False)))
        .annotate(
            completed_issues=Count(
                "id",
                filter=Q(
                    completed_at__isnull=False,
                    archived_at__isnull=True,
                    is_draft=False,
                ),
            )
        )
        .annotate(
            pending_issues=Count(
                "id",
                filter=Q(
                    completed_at__isnull=True,
                    archived_at__isnull=True,
                    is_draft=False,
                ),
            )
        )
        .order_by("label_name")
    )

    # Label distribution serialization
    label_distribution_data = [
        {
            "label_name": item["label_name"],
            "color": item["color"],
            "label_id": (str(item["label_id"]) if item["label_id"] else None),
            "total_issues": item["total_issues"],
            "completed_issues": item["completed_issues"],
            "pending_issues": item["pending_issues"],
        }
        for item in label_distribution
    ]

    # Generate completion chart
    completion_chart = burndown_plot(
        queryset=old_cycle,
        slug=slug,
        project_id=project_id,
        plot_type="issues",
        cycle_id=cycle_id,
    )

    # Get the current cycle and save progress snapshot
    current_cycle = Cycle.objects.filter(workspace__slug=slug, project_id=project_id, pk=cycle_id).first()

    current_cycle.progress_snapshot = {
        "total_issues": old_cycle.total_issues,
        "completed_issues": old_cycle.completed_issues,
        "cancelled_issues": old_cycle.cancelled_issues,
        "started_issues": old_cycle.started_issues,
        "unstarted_issues": old_cycle.unstarted_issues,
        "backlog_issues": old_cycle.backlog_issues,
        "distribution": {
            "labels": label_distribution_data,
            "assignees": assignee_distribution_data,
            "completion_chart": completion_chart,
        },
        "estimate_distribution": (
            {}
            if not estimate_type
            else {
                "labels": label_estimate_distribution,
                "assignees": assignee_estimate_distribution,
                "completion_chart": estimate_completion_chart,
            }
        ),
    }
    current_cycle.save(update_fields=["progress_snapshot"])

    # Get issues to transfer (only incomplete issues)
    cycle_issues = CycleIssue.objects.filter(
        cycle_id=cycle_id,
        project_id=project_id,
        workspace__slug=slug,
        issue__archived_at__isnull=True,
        issue__is_draft=False,
        issue__state__group__in=["backlog", "unstarted", "started"],
    )

    updated_cycles = []
    update_cycle_issue_activity = []
    for cycle_issue in cycle_issues:
        cycle_issue.cycle_id = new_cycle_id
        updated_cycles.append(cycle_issue)
        update_cycle_issue_activity.append(
            {
                "old_cycle_id": str(cycle_id),
                "new_cycle_id": str(new_cycle_id),
                "issue_id": str(cycle_issue.issue_id),
            }
        )

    # Bulk update cycle issues
    cycle_issues = CycleIssue.objects.bulk_update(updated_cycles, ["cycle_id"], batch_size=100)

    # Capture Issue Activity
    issue_activity.delay(
        type="cycle.activity.created",
        requested_data=json.dumps({"cycles_list": []}),
        actor_id=str(user_id),
        issue_id=None,
        project_id=str(project_id),
        current_instance=json.dumps(
            {
                "updated_cycle_issues": update_cycle_issue_activity,
                "created_cycle_issues": [],
            }
        ),
        epoch=int(timezone.now().timestamp()),
        notification=True,
        origin=base_host(request=request, is_app=True),
    )

    return {"success": True}
