"""CloudWatch Alarm usage report."""

from dataclasses import dataclass
from datetime import datetime

import boto3
from rich.console import Console

console = Console()


@dataclass
class AlarmInfo:
    """Basic CloudWatch Alarm info from AWS."""

    name: str
    arn: str
    namespace: str
    metric_name: str
    dimensions: dict[str, str]  # {dimension_name: dimension_value}
    state: str
    updated: datetime


@dataclass
class AlarmReport:
    """CloudWatch Alarm with orphan status."""

    name: str
    arn: str
    namespace: str
    metric_name: str
    dimensions: dict[str, str]
    state: str
    updated: datetime
    resource_exists: bool | None  # None = unknown/unsupported namespace

    @property
    def is_orphaned(self) -> bool:
        # Orphaned if we checked and resource doesn't exist
        return self.resource_exists is False

    def to_dict(self) -> dict:
        return {
            "name": self.name,
            "arn": self.arn,
            "namespace": self.namespace,
            "metric_name": self.metric_name,
            "dimensions": self.dimensions,
            "state": self.state,
            "updated": self.updated.isoformat(),
            "resource_exists": self.resource_exists,
            "is_orphaned": self.is_orphaned,
        }


def get_alarms(session: boto3.Session) -> list[AlarmInfo]:
    """Fetch all CloudWatch metric alarms."""
    cw = session.client("cloudwatch")
    paginator = cw.get_paginator("describe_alarms")

    alarms = []
    for page in paginator.paginate(AlarmTypes=["MetricAlarm"]):
        for alarm in page["MetricAlarms"]:
            dimensions = {d["Name"]: d["Value"] for d in alarm.get("Dimensions", [])}
            alarms.append(
                AlarmInfo(
                    name=alarm["AlarmName"],
                    arn=alarm["AlarmArn"],
                    namespace=alarm.get("Namespace", ""),
                    metric_name=alarm.get("MetricName", ""),
                    dimensions=dimensions,
                    state=alarm["StateValue"],
                    updated=alarm["StateUpdatedTimestamp"],
                )
            )

    console.print(f"Found {len(alarms)} CloudWatch alarms")
    return alarms


def get_rds_instances(session: boto3.Session) -> set[str]:
    """Get all RDS instance identifiers (includes Aurora, DocDB, Neptune)."""
    rds = session.client("rds")
    paginator = rds.get_paginator("describe_db_instances")

    instances = set()
    for page in paginator.paginate():
        for db in page["DBInstances"]:
            instances.add(db["DBInstanceIdentifier"])

    console.print(f"Found {len(instances)} RDS/DocDB instances")
    return instances


def get_mq_brokers(session: boto3.Session) -> set[str]:
    """Get all Amazon MQ broker names."""
    mq = session.client("mq")

    brokers = set()
    next_token = None

    while True:
        kwargs = {}
        if next_token:
            kwargs["NextToken"] = next_token

        response = mq.list_brokers(**kwargs)
        for broker in response.get("BrokerSummaries", []):
            brokers.add(broker["BrokerName"])

        next_token = response.get("NextToken")
        if not next_token:
            break

    console.print(f"Found {len(brokers)} MQ brokers")
    return brokers


def get_sqs_queues(session: boto3.Session) -> set[str]:
    """Get all SQS queue names."""
    sqs = session.client("sqs")
    paginator = sqs.get_paginator("list_queues")

    queues = set()
    for page in paginator.paginate():
        for url in page.get("QueueUrls", []):
            # Queue name is the last part of the URL
            queue_name = url.rsplit("/", 1)[-1]
            queues.add(queue_name)

    console.print(f"Found {len(queues)} SQS queues")
    return queues


def get_ec2_instances(session: boto3.Session) -> set[str]:
    """Get all EC2 instance IDs (running and stopped)."""
    ec2 = session.client("ec2")
    paginator = ec2.get_paginator("describe_instances")

    instances = set()
    for page in paginator.paginate(
        Filters=[{"Name": "instance-state-name", "Values": ["running", "stopped"]}]
    ):
        for reservation in page["Reservations"]:
            for instance in reservation["Instances"]:
                instances.add(instance["InstanceId"])

    console.print(f"Found {len(instances)} EC2 instances")
    return instances


def get_elasticache_clusters(session: boto3.Session) -> set[str]:
    """Get all ElastiCache cluster IDs."""
    elasticache = session.client("elasticache")
    paginator = elasticache.get_paginator("describe_cache_clusters")

    clusters = set()
    for page in paginator.paginate():
        for cluster in page["CacheClusters"]:
            clusters.add(cluster["CacheClusterId"])

    console.print(f"Found {len(clusters)} ElastiCache clusters")
    return clusters


def get_elb_classic(session: boto3.Session) -> set[str]:
    """Get all Classic ELB names."""
    elb = session.client("elb")
    paginator = elb.get_paginator("describe_load_balancers")

    load_balancers = set()
    for page in paginator.paginate():
        for lb in page["LoadBalancerDescriptions"]:
            load_balancers.add(lb["LoadBalancerName"])

    console.print(f"Found {len(load_balancers)} Classic ELBs")
    return load_balancers


def get_elbv2_load_balancers(session: boto3.Session) -> set[str]:
    """Get all ALB/NLB ARN suffixes (app/name/id or net/name/id)."""
    elbv2 = session.client("elbv2")
    paginator = elbv2.get_paginator("describe_load_balancers")

    load_balancers = set()
    for page in paginator.paginate():
        for lb in page["LoadBalancers"]:
            # ARN format: arn:aws:elasticloadbalancing:region:account:loadbalancer/app/name/id
            # Dimension uses: app/name/id
            arn = lb["LoadBalancerArn"]
            suffix = arn.split(":loadbalancer/")[-1]
            load_balancers.add(suffix)

    console.print(f"Found {len(load_balancers)} ALB/NLBs")
    return load_balancers


def get_opensearch_domains(session: boto3.Session) -> set[str]:
    """Get all OpenSearch/Elasticsearch domain names."""
    # Try OpenSearch first (newer), fall back to ES
    try:
        client = session.client("opensearch")
        response = client.list_domain_names()
    except Exception:
        client = session.client("es")
        response = client.list_domain_names()

    domains = {d["DomainName"] for d in response.get("DomainNames", [])}
    console.print(f"Found {len(domains)} OpenSearch domains")
    return domains


def get_route53_health_checks(session: boto3.Session) -> set[str]:
    """Get all Route53 health check IDs."""
    route53 = session.client("route53")
    paginator = route53.get_paginator("list_health_checks")

    health_checks = set()
    for page in paginator.paginate():
        for hc in page["HealthChecks"]:
            health_checks.add(hc["Id"])

    console.print(f"Found {len(health_checks)} Route53 health checks")
    return health_checks


def get_docdb_clusters(session: boto3.Session) -> set[str]:
    """Get all DocumentDB cluster identifiers."""
    docdb = session.client("docdb")
    paginator = docdb.get_paginator("describe_db_clusters")

    clusters = set()
    for page in paginator.paginate():
        for cluster in page["DBClusters"]:
            clusters.add(cluster["DBClusterIdentifier"])

    console.print(f"Found {len(clusters)} DocDB clusters")
    return clusters


def get_auto_scaling_groups(session: boto3.Session) -> set[str]:
    """Get all Auto Scaling Group names."""
    autoscaling = session.client("autoscaling")
    paginator = autoscaling.get_paginator("describe_auto_scaling_groups")

    groups = set()
    for page in paginator.paginate():
        for asg in page["AutoScalingGroups"]:
            groups.add(asg["AutoScalingGroupName"])

    console.print(f"Found {len(groups)} Auto Scaling Groups")
    return groups


def check_resource_exists(
    alarm: AlarmInfo,
    rds_instances: set[str],
    mq_brokers: set[str],
    sqs_queues: set[str],
    ec2_instances: set[str],
    elasticache_clusters: set[str],
    elb_classic: set[str],
    elbv2_load_balancers: set[str],
    opensearch_domains: set[str],
    route53_health_checks: set[str],
    docdb_clusters: set[str],
    auto_scaling_groups: set[str],
) -> bool | None:
    """Check if the resource referenced by alarm exists.

    Returns:
        True: resource exists
        False: resource doesn't exist (orphaned)
        None: unsupported namespace, can't determine
    """
    namespace = alarm.namespace
    dims = alarm.dimensions

    # RDS and DocDB instances
    if namespace in ("AWS/RDS", "AWS/DocDB"):
        instance_id = dims.get("DBInstanceIdentifier")
        if instance_id:
            return instance_id in rds_instances
        # Check cluster identifier for DocDB
        cluster_id = dims.get("DBClusterIdentifier")
        if cluster_id:
            return cluster_id in docdb_clusters
        return None

    # Amazon MQ
    if namespace == "AWS/AmazonMQ":
        broker = dims.get("Broker")
        if broker:
            return broker in mq_brokers
        return None

    # SQS
    if namespace == "AWS/SQS":
        queue = dims.get("QueueName")
        if queue:
            return queue in sqs_queues
        return None

    # EC2
    if namespace == "AWS/EC2":
        instance_id = dims.get("InstanceId")
        if instance_id:
            return instance_id in ec2_instances
        asg_name = dims.get("AutoScalingGroupName")
        if asg_name:
            return asg_name in auto_scaling_groups
        return None

    # ElastiCache
    if namespace == "AWS/ElastiCache":
        cluster_id = dims.get("CacheClusterId")
        if cluster_id:
            return cluster_id in elasticache_clusters
        return None

    # Classic ELB
    if namespace == "AWS/ELB":
        lb_name = dims.get("LoadBalancerName")
        if lb_name:
            return lb_name in elb_classic
        return None

    # ALB/NLB
    if namespace == "AWS/ApplicationELB":
        lb = dims.get("LoadBalancer")
        if lb:
            return lb in elbv2_load_balancers
        return None

    # OpenSearch / Elasticsearch
    if namespace in ("AWS/ES", "AWS/OpenSearch"):
        domain = dims.get("DomainName")
        if domain:
            return domain in opensearch_domains
        return None

    # Route53 health checks
    if namespace == "AWS/Route53":
        hc_id = dims.get("HealthCheckId")
        if hc_id:
            return hc_id in route53_health_checks
        return None

    # Unsupported namespace
    return None


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

    # Fetch alarms
    alarms = get_alarms(session)

    # Get all resources for supported namespaces
    rds_instances = get_rds_instances(session)
    mq_brokers = get_mq_brokers(session)
    sqs_queues = get_sqs_queues(session)
    ec2_instances = get_ec2_instances(session)
    elasticache_clusters = get_elasticache_clusters(session)
    elb_classic = get_elb_classic(session)
    elbv2_load_balancers = get_elbv2_load_balancers(session)
    opensearch_domains = get_opensearch_domains(session)
    route53_health_checks = get_route53_health_checks(session)
    docdb_clusters = get_docdb_clusters(session)
    auto_scaling_groups = get_auto_scaling_groups(session)

    # Build report
    report = []
    for alarm in alarms:
        resource_exists = check_resource_exists(
            alarm,
            rds_instances,
            mq_brokers,
            sqs_queues,
            ec2_instances,
            elasticache_clusters,
            elb_classic,
            elbv2_load_balancers,
            opensearch_domains,
            route53_health_checks,
            docdb_clusters,
            auto_scaling_groups,
        )
        report.append(
            AlarmReport(
                name=alarm.name,
                arn=alarm.arn,
                namespace=alarm.namespace,
                metric_name=alarm.metric_name,
                dimensions=alarm.dimensions,
                state=alarm.state,
                updated=alarm.updated,
                resource_exists=resource_exists,
            )
        )

    return report


def delete_alarm(session: boto3.Session, alarm_name: str) -> None:
    """Delete a CloudWatch alarm."""
    cw = session.client("cloudwatch")
    cw.delete_alarms(AlarmNames=[alarm_name])
    console.print(f"  Deleted alarm {alarm_name}")
