"""
Findings aggregator for consolidating compliance test findings.

This module provides utilities to aggregate and group findings from
compliance tests, consolidating similar issues across multiple resources.

Example:
    >>> from complio.utils.aggregator import aggregate_findings
    >>> aggregated = aggregate_findings(scan_results)
    >>> for finding in aggregated:
    ...     print(f"{finding['title']} — {finding['resource_count']} resources")
"""

from collections import defaultdict
from dataclasses import dataclass
from typing import Dict, List

from complio.core.runner import ScanResults
from complio.tests_library.base import Finding, Severity, TestResult


@dataclass
class AggregatedFinding:
    """Aggregated finding with resource count.

    Attributes:
        severity: Finding severity
        title: Finding title/description
        test_id: ID of the compliance test
        test_name: Name of the compliance test
        resource_count: Number of affected resources
        resource_ids: List of affected resource IDs
        resource_type: Type of resources (e.g., "buckets", "VPCs", "NACLs")
    """
    severity: str
    title: str
    test_id: str
    test_name: str
    resource_count: int
    resource_ids: List[str]
    resource_type: str


def _infer_resource_type(test_id: str, resource_ids: List[str]) -> str:
    """Infer resource type from test ID and resource IDs.

    Args:
        test_id: Test identifier
        resource_ids: List of resource IDs

    Returns:
        Human-readable resource type (e.g., "buckets", "VPCs")
    """
    # Resource type mapping based on test ID
    type_map = {
        "s3": "buckets",
        "vpc": "VPCs",
        "nacl": "NACLs",
        "ec2": "instances",
        "rds": "databases",
        "kms": "keys",
        "iam": "policies",
        "cloudtrail": "trails",
        "cloudwatch": "log groups",
        "lambda": "functions",
        "efs": "file systems",
        "ebs": "volumes",
        "redshift": "clusters",
        "dynamodb": "tables",
        "elasticache": "clusters",
        "secrets_manager": "secrets",
        "backup": "vaults",
        "sns": "topics",
        "sqs": "queues",
        "alb": "load balancers",
        "nlb": "load balancers",
        "cloudfront": "distributions",
        "api_gateway": "APIs",
        "waf": "web ACLs",
        "guardduty": "detectors",
        "security_hub": "hubs",
        "config": "recorders",
        "eventbridge": "rules",
        "network_firewall": "firewalls",
        "transit_gateway": "transit gateways",
        "vpc_endpoint": "endpoints",
        "vpn": "VPN connections",
        "direct_connect": "connections",
    }

    # Try to match test_id to resource type
    for key, value in type_map.items():
        if key in test_id.lower():
            return value

    # If no match, try to infer from resource IDs
    if resource_ids:
        first_id = resource_ids[0].lower()
        if "bucket" in first_id or first_id.startswith("arn:aws:s3"):
            return "buckets"
        elif "vpc-" in first_id:
            return "VPCs"
        elif "acl-" in first_id:
            return "NACLs"
        elif "sg-" in first_id:
            return "security groups"
        elif "vol-" in first_id:
            return "volumes"
        elif "i-" in first_id:
            return "instances"

    return "resources"


def aggregate_findings(results: ScanResults) -> List[AggregatedFinding]:
    """Aggregate findings by test and title.

    Groups findings with the same test ID and title together,
    counting affected resources and consolidating them.

    Args:
        results: Scan results containing test results and findings

    Returns:
        List of aggregated findings sorted by severity

    Example:
        >>> aggregated = aggregate_findings(scan_results)
        >>> for finding in aggregated:
        ...     print(f"{finding.severity}: {finding.title} — {finding.resource_count} {finding.resource_type}")
        CRITICAL: Root account MFA not enabled — 1 accounts
        HIGH: S3 public access block missing — 4 buckets
        HIGH: VPC Flow Logs disabled — 2 VPCs
    """
    # Group findings by (test_id, title)
    grouped: Dict[tuple, Dict] = defaultdict(lambda: {
        "severity": None,
        "title": "",
        "test_id": "",
        "test_name": "",
        "resource_ids": [],
    })

    for test_result in results.test_results:
        for finding in test_result.findings:
            # Create grouping key
            key = (test_result.test_id, finding.title)

            # Add to group
            group = grouped[key]
            group["severity"] = finding.severity
            group["title"] = finding.title
            group["test_id"] = test_result.test_id
            group["test_name"] = test_result.test_name
            group["resource_ids"].append(finding.resource_id)

    # Convert to AggregatedFinding objects
    aggregated = []
    for (test_id, title), data in grouped.items():
        resource_type = _infer_resource_type(test_id, data["resource_ids"])

        aggregated.append(AggregatedFinding(
            severity=data["severity"],
            title=data["title"],
            test_id=data["test_id"],
            test_name=data["test_name"],
            resource_count=len(data["resource_ids"]),
            resource_ids=data["resource_ids"],
            resource_type=resource_type,
        ))

    # Sort by severity (critical > high > medium > low)
    severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3}
    aggregated.sort(key=lambda f: (severity_order.get(f.severity, 99), f.title))

    return aggregated


def group_findings_by_severity(aggregated: List[AggregatedFinding]) -> Dict[str, List[AggregatedFinding]]:
    """Group aggregated findings by severity level.

    Args:
        aggregated: List of aggregated findings

    Returns:
        Dictionary mapping severity to list of findings

    Example:
        >>> by_severity = group_findings_by_severity(aggregated)
        >>> print(f"Critical: {len(by_severity['critical'])}")
        >>> print(f"High: {len(by_severity['high'])}")
    """
    grouped = defaultdict(list)

    for finding in aggregated:
        grouped[finding.severity].append(finding)

    return dict(grouped)
