"""Launch Template usage report."""

from dataclasses import dataclass, field
from datetime import datetime

import boto3
from rich.console import Console

console = Console()

KARPENTER_PREFIX = "karpenter.k8s.aws/"


@dataclass
class Reference:
    """A reference to a Launch Template from another AWS resource."""

    type: str  # ASG, EKS, EC2Fleet, SpotFleet
    name: str  # human-readable identifier


@dataclass
class LaunchTemplateInfo:
    """Basic Launch Template info from AWS."""

    id: str
    name: str
    created: datetime


@dataclass
class LaunchTemplateReport:
    """Launch Template with its references."""

    id: str
    name: str
    created: datetime
    instance_count: int = 0
    references: list[Reference] = field(default_factory=list)

    @property
    def is_orphaned(self) -> bool:
        return len(self.references) == 0 and self.instance_count == 0

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "name": self.name,
            "created": self.created.isoformat(),
            "instance_count": self.instance_count,
            "references": [{"type": r.type, "name": r.name} for r in self.references],
            "is_orphaned": self.is_orphaned,
        }


def get_launch_templates(session: boto3.Session) -> dict[str, LaunchTemplateInfo]:
    """Fetch all Launch Templates. Returns {lt_id: LaunchTemplateInfo}."""
    ec2 = session.client("ec2")
    paginator = ec2.get_paginator("describe_launch_templates")

    templates = {}
    for page in paginator.paginate():
        for lt in page["LaunchTemplates"]:
            templates[lt["LaunchTemplateId"]] = LaunchTemplateInfo(
                id=lt["LaunchTemplateId"],
                name=lt["LaunchTemplateName"],
                created=lt["CreateTime"],
            )

    console.print(f"Found {len(templates)} launch templates")
    return templates


def get_asg_references(session: boto3.Session) -> dict[str, list[Reference]]:
    """Get Launch Template references from Auto Scaling Groups."""
    autoscaling = session.client("autoscaling")
    paginator = autoscaling.get_paginator("describe_auto_scaling_groups")

    refs: dict[str, list[Reference]] = {}
    for page in paginator.paginate():
        for asg in page["AutoScalingGroups"]:
            lt = asg.get("LaunchTemplate")
            if lt:
                lt_id = lt.get("LaunchTemplateId")
                if lt_id:
                    refs.setdefault(lt_id, []).append(
                        Reference(type="ASG", name=asg["AutoScalingGroupName"])
                    )

            # Check MixedInstancesPolicy
            mixed = asg.get("MixedInstancesPolicy")
            if mixed:
                launch_template = mixed.get("LaunchTemplate", {})
                lt_spec = launch_template.get("LaunchTemplateSpecification", {})
                lt_id = lt_spec.get("LaunchTemplateId")
                if lt_id:
                    refs.setdefault(lt_id, []).append(
                        Reference(type="ASG", name=asg["AutoScalingGroupName"])
                    )

    console.print(f"Found {sum(len(v) for v in refs.values())} ASG references")
    return refs


def get_eks_references(session: boto3.Session) -> dict[str, list[Reference]]:
    """Get Launch Template references from EKS Node Groups."""
    eks = session.client("eks")

    refs: dict[str, list[Reference]] = {}

    # List all clusters
    clusters = []
    paginator = eks.get_paginator("list_clusters")
    for page in paginator.paginate():
        clusters.extend(page["clusters"])

    # For each cluster, list node groups and check launch templates
    for cluster_name in clusters:
        ng_paginator = eks.get_paginator("list_nodegroups")
        for ng_page in ng_paginator.paginate(clusterName=cluster_name):
            for ng_name in ng_page["nodegroups"]:
                ng = eks.describe_nodegroup(
                    clusterName=cluster_name, nodegroupName=ng_name
                )["nodegroup"]

                lt = ng.get("launchTemplate")
                if lt:
                    lt_id = lt.get("id")
                    if lt_id:
                        refs.setdefault(lt_id, []).append(
                            Reference(type="EKS", name=f"{cluster_name}/{ng_name}")
                        )

    console.print(f"Found {sum(len(v) for v in refs.values())} EKS references")
    return refs


def get_ec2_fleet_references(session: boto3.Session) -> dict[str, list[Reference]]:
    """Get Launch Template references from EC2 Fleets."""
    ec2 = session.client("ec2")
    paginator = ec2.get_paginator("describe_fleets")

    refs: dict[str, list[Reference]] = {}
    for page in paginator.paginate():
        for fleet in page["Fleets"]:
            fleet_id = fleet["FleetId"]
            for lt_config in fleet.get("LaunchTemplateConfigs", []):
                lt_spec = lt_config.get("LaunchTemplateSpecification", {})
                lt_id = lt_spec.get("LaunchTemplateId")
                if lt_id:
                    refs.setdefault(lt_id, []).append(
                        Reference(type="EC2Fleet", name=fleet_id)
                    )

    console.print(f"Found {sum(len(v) for v in refs.values())} EC2 Fleet references")
    return refs


def get_spot_fleet_references(session: boto3.Session) -> dict[str, list[Reference]]:
    """Get Launch Template references from Spot Fleet Requests."""
    ec2 = session.client("ec2")
    paginator = ec2.get_paginator("describe_spot_fleet_requests")

    refs: dict[str, list[Reference]] = {}
    for page in paginator.paginate():
        for sfr in page["SpotFleetRequestConfigs"]:
            sfr_id = sfr["SpotFleetRequestId"]
            config = sfr.get("SpotFleetRequestConfig", {})
            for lt_config in config.get("LaunchTemplateConfigs", []):
                lt_spec = lt_config.get("LaunchTemplateSpecification", {})
                lt_id = lt_spec.get("LaunchTemplateId")
                if lt_id:
                    refs.setdefault(lt_id, []).append(
                        Reference(type="SpotFleet", name=sfr_id)
                    )

    console.print(f"Found {sum(len(v) for v in refs.values())} Spot Fleet references")
    return refs


def get_ec2_instance_counts(session: boto3.Session) -> dict[str, int]:
    """Get running EC2 instance counts per Launch Template.

    EC2 instances launched from a launch template have automatic tags:
    - aws:ec2launchtemplate:id
    - aws:ec2launchtemplate:version
    """
    ec2 = session.client("ec2")
    paginator = ec2.get_paginator("describe_instances")

    counts: dict[str, int] = {}
    for page in paginator.paginate(
        Filters=[{"Name": "instance-state-name", "Values": ["running"]}]
    ):
        for reservation in page["Reservations"]:
            for instance in reservation["Instances"]:
                tags = {t["Key"]: t["Value"] for t in instance.get("Tags", [])}
                lt_id = tags.get("aws:ec2launchtemplate:id")
                if lt_id:
                    counts[lt_id] = counts.get(lt_id, 0) + 1

    console.print(f"Found {sum(counts.values())} running EC2 instances")
    return counts


def build_report(
    session: boto3.Session | None = None,
) -> list[LaunchTemplateReport]:
    """Build Launch Template usage report."""
    if session is None:
        session = boto3.Session()

    # Fetch all data
    templates = get_launch_templates(session)
    asg_refs = get_asg_references(session)
    eks_refs = get_eks_references(session)
    ec2_fleet_refs = get_ec2_fleet_references(session)
    spot_fleet_refs = get_spot_fleet_references(session)
    instance_counts = get_ec2_instance_counts(session)

    # Merge references
    all_refs: dict[str, list[Reference]] = {}
    for refs in [asg_refs, eks_refs, ec2_fleet_refs, spot_fleet_refs]:
        for lt_id, ref_list in refs.items():
            all_refs.setdefault(lt_id, []).extend(ref_list)

    # Build report
    report = []
    for lt_id, lt_info in templates.items():
        refs = all_refs.get(lt_id, [])

        # Detect Karpenter-managed launch templates by naming convention
        if lt_info.name.startswith(KARPENTER_PREFIX):
            refs.append(Reference(type="Karpenter", name="managed"))

        report.append(
            LaunchTemplateReport(
                id=lt_id,
                name=lt_info.name,
                created=lt_info.created,
                instance_count=instance_counts.get(lt_id, 0),
                references=refs,
            )
        )

    return report
