from abc import ABC, abstractmethod

import boto3

from .arnparse import arnparse


class Handler(ABC):
    @classmethod
    @abstractmethod
    def get_tags(cls, session, arns):
        pass

    @classmethod
    @abstractmethod
    def set_tags(cls, session, arns, tags):
        pass


class IAMHandler(Handler):
    """Handler for IAM resources using dedicated IAM tagging API."""

    @classmethod
    def get_tags(cls, session, arns):
        if not arns:
            raise ValueError("At least one ARN must be provided to get_tags.")

        client = session.client("iam")
        result = {}

        for arn in arns:
            arn_data = arnparse(arn)
            resource_type = arn_data.resource_type
            resource_name = arn_data.resource_name

            if resource_type == "role":
                response = client.list_role_tags(RoleName=resource_name)
            elif resource_type == "user":
                response = client.list_user_tags(UserName=resource_name)
            elif resource_type == "policy":
                response = client.list_policy_tags(PolicyArn=arn)
            elif resource_type == "instance-profile":
                response = client.list_instance_profile_tags(
                    InstanceProfileName=resource_name
                )
            else:
                response = None

            if response:
                result[arn] = {t["Key"]: t["Value"] for t in response["Tags"]}
            else:
                result[arn] = None

        return result

    @classmethod
    def set_tags(cls, session, arns, tags):
        if not arns:
            raise ValueError("At least one ARN must be provided to set_tags.")

        client = session.client("iam")
        result = {}
        payload = [{"Key": k, "Value": v} for k, v in tags.items()]

        for arn in arns:
            arn_data = arnparse(arn)
            resource_type = arn_data.resource_type
            resource_name = arn_data.resource_name

            try:
                if resource_type == "role":
                    response = client.tag_role(RoleName=resource_name, Tags=payload)
                elif resource_type == "user":
                    response = client.tag_user(UserName=resource_name, Tags=payload)
                elif resource_type == "policy":
                    response = client.tag_policy(PolicyArn=arn, Tags=payload)
                elif resource_type == "instance-profile":
                    response = client.tag_instance_profile(
                        InstanceProfileName=resource_name, Tags=payload
                    )
                else:
                    result[arn] = None
                    continue
            except client.exceptions.NoSuchEntityException:
                result[arn] = None
                continue

            result[arn] = response["ResponseMetadata"]["HTTPStatusCode"] == 200

        return result


class TaggingAPIHandler(Handler):
    @classmethod
    def get_tags(cls, session, arns):
        if not arns:
            raise ValueError("At least one ARN must be provided to get_tags.")

        client = session.client("resourcegroupstaggingapi")
        result = {}

        # https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/API_GetResources.html
        batch_size = 100
        for i in range(0, len(arns), batch_size):
            batch = arns[i : i + batch_size]
            response = client.get_resources(ResourceARNList=batch)
            response = response["ResourceTagMappingList"]
            for arn in batch:
                if arn in response:
                    result[arn] = {
                        t["Key"]: t["Value"] for t in response[arn].get("Tags", [])
                    }
                else:
                    result[arn] = None
        return result

    @classmethod
    def set_tags(cls, session, arns, tags):
        if not arns:
            raise ValueError("At least one ARN must be provided to set_tags.")

        if len(tags) == 0:
            raise ValueError("At least one tag must be provided to set_tags.")

        if len(tags) > 50:
            raise ValueError("A maximum of 50 tags can be set at once.")

        client = session.client("resourcegroupstaggingapi")
        result = {}

        # https://docs.aws.amazon.com/resourcegroupstagging/latest/APIReference/API_TagResources.html
        batch_size = 20
        for i in range(0, len(arns), batch_size):
            batch = arns[i : i + batch_size]
            response = client.tag_resources(ResourceARNList=batch, Tags=tags)
            for arn in batch:
                result[arn] = arn not in response["FailedResourcesMap"]

        return result
