"""AWS cloud resource state reader.

Reads live state of AWS resources using boto3.
Falls back to AWS CLI when SDK is unavailable.
"""

from __future__ import annotations

import json
import logging
import os
import subprocess
from dataclasses import dataclass, field
from typing import Any, Optional

logger = logging.getLogger(__name__)


@dataclass
class AWSResourceState:
    """Live state of an AWS resource."""
    arn: str
    resource_id: str
    resource_type: str
    region: str
    name: str
    tags: dict[str, str] = field(default_factory=dict)
    properties: dict[str, Any] = field(default_factory=dict)
    exists: bool = True


# Maps Terraform resource types to AWS API calls
_RESOURCE_READERS: dict[str, dict[str, str]] = {
    "aws_instance": {"service": "ec2", "method": "describe_instances", "id_field": "InstanceId"},
    "aws_security_group": {"service": "ec2", "method": "describe_security_groups", "id_field": "GroupId"},
    "aws_s3_bucket": {"service": "s3", "method": "get_bucket_location", "id_field": "Bucket"},
    "aws_db_instance": {"service": "rds", "method": "describe_db_instances", "id_field": "DBInstanceIdentifier"},
    "aws_lambda_function": {"service": "lambda", "method": "get_function", "id_field": "FunctionName"},
    "aws_iam_role": {"service": "iam", "method": "get_role", "id_field": "RoleName"},
}


class AWSProvider:
    """Read live AWS resource state.

    Authentication uses standard AWS credential chain:
      - AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
      - AWS_PROFILE
      - Instance metadata
    """

    def __init__(self, region: Optional[str] = None, profile: Optional[str] = None) -> None:
        self.region = region or os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
        self.profile = profile or os.environ.get("AWS_PROFILE")
        self._sdk_available = False
        self._session = None
        self._try_init_sdk()

    def _try_init_sdk(self) -> None:
        """Attempt to initialize boto3 session."""
        try:
            import boto3
            kwargs: dict[str, Any] = {"region_name": self.region}
            if self.profile:
                kwargs["profile_name"] = self.profile
            self._session = boto3.Session(**kwargs)
            self._sdk_available = True
            logger.info("AWS SDK (boto3) initialized successfully")
        except ImportError:
            logger.info("boto3 not installed, will use CLI fallback")
        except Exception as e:
            logger.warning("boto3 init failed: %s", e)

    def get_ec2_instance(self, instance_id: str) -> AWSResourceState:
        """Get the live state of an EC2 instance."""
        if self._sdk_available:
            try:
                ec2 = self._session.client("ec2")
                resp = ec2.describe_instances(InstanceIds=[instance_id])
                reservations = resp.get("Reservations", [])
                if not reservations or not reservations[0].get("Instances"):
                    return AWSResourceState(arn="", resource_id=instance_id,
                                            resource_type="aws_instance", region=self.region,
                                            name="", exists=False)
                inst = reservations[0]["Instances"][0]
                tags = {t["Key"]: t["Value"] for t in inst.get("Tags", [])}
                return AWSResourceState(
                    arn=inst.get("InstanceId", ""),
                    resource_id=instance_id,
                    resource_type="aws_instance",
                    region=self.region,
                    name=tags.get("Name", ""),
                    tags=tags,
                    properties={
                        "instance_type": inst.get("InstanceType"),
                        "ami": inst.get("ImageId"),
                        "state": inst.get("State", {}).get("Name"),
                        "vpc_id": inst.get("VpcId"),
                        "subnet_id": inst.get("SubnetId"),
                        "public_ip": inst.get("PublicIpAddress"),
                        "private_ip": inst.get("PrivateIpAddress"),
                        "security_groups": [sg["GroupId"] for sg in inst.get("SecurityGroups", [])],
                        "ebs_optimized": inst.get("EbsOptimized"),
                        "monitoring": inst.get("Monitoring", {}).get("State"),
                    },
                    exists=True,
                )
            except Exception as e:
                if "InvalidInstanceID.NotFound" in str(e):
                    return AWSResourceState(arn="", resource_id=instance_id,
                                            resource_type="aws_instance", region=self.region,
                                            name="", exists=False)
                logger.warning("SDK get_ec2_instance failed: %s", e)

        return self._get_via_cli("ec2", "describe-instances", instance_id)

    def get_security_group(self, group_id: str) -> AWSResourceState:
        """Get the live state of a security group."""
        if self._sdk_available:
            try:
                ec2 = self._session.client("ec2")
                resp = ec2.describe_security_groups(GroupIds=[group_id])
                groups = resp.get("SecurityGroups", [])
                if not groups:
                    return AWSResourceState(arn="", resource_id=group_id,
                                            resource_type="aws_security_group", region=self.region,
                                            name="", exists=False)
                sg = groups[0]
                tags = {t["Key"]: t["Value"] for t in sg.get("Tags", [])}
                return AWSResourceState(
                    arn=sg.get("GroupId", ""),
                    resource_id=group_id,
                    resource_type="aws_security_group",
                    region=self.region,
                    name=sg.get("GroupName", ""),
                    tags=tags,
                    properties={
                        "description": sg.get("Description"),
                        "vpc_id": sg.get("VpcId"),
                        "ingress": [
                            {
                                "from_port": rule.get("FromPort"),
                                "to_port": rule.get("ToPort"),
                                "protocol": rule.get("IpProtocol"),
                                "cidr_blocks": [r["CidrIp"] for r in rule.get("IpRanges", [])],
                            }
                            for rule in sg.get("IpPermissions", [])
                        ],
                        "egress": [
                            {
                                "from_port": rule.get("FromPort"),
                                "to_port": rule.get("ToPort"),
                                "protocol": rule.get("IpProtocol"),
                                "cidr_blocks": [r["CidrIp"] for r in rule.get("IpRanges", [])],
                            }
                            for rule in sg.get("IpPermissionsEgress", [])
                        ],
                    },
                    exists=True,
                )
            except Exception as e:
                logger.warning("SDK get_security_group failed: %s", e)

        return self._get_via_cli("ec2", "describe-security-groups", group_id)

    def get_s3_bucket(self, bucket_name: str) -> AWSResourceState:
        """Get the live state of an S3 bucket."""
        if self._sdk_available:
            try:
                s3 = self._session.client("s3")
                # Check existence
                s3.head_bucket(Bucket=bucket_name)
                # Get properties
                acl = s3.get_bucket_acl(Bucket=bucket_name)
                versioning = s3.get_bucket_versioning(Bucket=bucket_name)
                try:
                    encryption = s3.get_bucket_encryption(Bucket=bucket_name)
                    enc_rules = encryption.get("ServerSideEncryptionConfiguration", {}).get("Rules", [])
                except Exception:
                    enc_rules = []
                try:
                    public_access = s3.get_public_access_block(Bucket=bucket_name)
                    pub_config = public_access.get("PublicAccessBlockConfiguration", {})
                except Exception:
                    pub_config = {}
                try:
                    logging_config = s3.get_bucket_logging(Bucket=bucket_name)
                    logging_enabled = logging_config.get("LoggingEnabled") is not None
                except Exception:
                    logging_enabled = False

                return AWSResourceState(
                    arn=f"arn:aws:s3:::{bucket_name}",
                    resource_id=bucket_name,
                    resource_type="aws_s3_bucket",
                    region=self.region,
                    name=bucket_name,
                    properties={
                        "versioning_enabled": versioning.get("Status") == "Enabled",
                        "encryption_rules": enc_rules,
                        "public_access_block": pub_config,
                        "logging_enabled": logging_enabled,
                        "acl_grants": len(acl.get("Grants", [])),
                    },
                    exists=True,
                )
            except Exception as e:
                if "404" in str(e) or "NoSuchBucket" in str(e):
                    return AWSResourceState(arn="", resource_id=bucket_name,
                                            resource_type="aws_s3_bucket", region=self.region,
                                            name=bucket_name, exists=False)
                logger.warning("SDK get_s3_bucket failed: %s", e)

        return self._get_via_cli("s3api", "get-bucket-location", bucket_name)

    def _get_via_cli(self, service: str, command: str, resource_id: str) -> AWSResourceState:
        """Fallback to AWS CLI."""
        try:
            cmd = ["aws", service, command, "--output", "json"]
            if self.region:
                cmd.extend(["--region", self.region])
            if self.profile:
                cmd.extend(["--profile", self.profile])
            # Add resource identifier based on command
            if "instance" in command:
                cmd.extend(["--instance-ids", resource_id])
            elif "security-group" in command:
                cmd.extend(["--group-ids", resource_id])
            elif "bucket" in command:
                cmd.extend(["--bucket", resource_id])

            result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
            if result.returncode != 0:
                return AWSResourceState(arn="", resource_id=resource_id,
                                        resource_type=f"aws_{service}", region=self.region,
                                        name="", exists=False)
            data = json.loads(result.stdout)
            return AWSResourceState(
                arn=resource_id,
                resource_id=resource_id,
                resource_type=f"aws_{service}",
                region=self.region,
                name=resource_id,
                properties=data,
                exists=True,
            )
        except Exception as e:
            logger.warning("CLI fallback failed: %s", e)
            return AWSResourceState(arn="", resource_id=resource_id,
                                    resource_type=f"aws_{service}", region=self.region,
                                    name="", exists=False)

    def flatten_to_attributes(self, state: AWSResourceState) -> dict[str, Any]:
        """Flatten AWSResourceState to attribute dict for comparison."""
        attrs: dict[str, Any] = {
            "name": state.name,
            "region": state.region,
        }
        if state.tags:
            for k, v in state.tags.items():
                attrs[f"tags.{k}"] = v
        for k, v in state.properties.items():
            if isinstance(v, (str, int, float, bool)):
                attrs[k] = v
            elif isinstance(v, list):
                attrs[k] = v
            elif isinstance(v, dict):
                for k2, v2 in v.items():
                    attrs[f"{k}.{k2}"] = v2
        return attrs
