from typing import Any, Optional

from moto.utilities.utils import filter_resources, get_partition

from ..utils import describe_tag_filter, random_managed_prefix_list_id
from .core import TaggedEC2Resource


class ManagedPrefixList(TaggedEC2Resource):
    def __init__(
        self,
        backend: Any,
        region: str,
        address_family: Optional[str] = None,
        entry: Optional[list[dict[str, str]]] = None,
        max_entries: Optional[str] = None,
        prefix_list_name: Optional[str] = None,
        tags: Optional[dict[str, str]] = None,
        owner_id: Optional[str] = None,
    ):
        self.ec2_backend = backend
        self.address_family = address_family
        self.max_entries = max_entries
        self.id = random_managed_prefix_list_id()
        self.prefix_list_name = prefix_list_name
        self.state = "create-complete"
        self.state_message = None
        self.add_tags(tags or {})
        self.version: Optional[int] = 1
        self.entries = {self.version: entry} if entry else {}
        self.resource_owner_id = owner_id if owner_id else None
        self.prefix_list_arn = self.arn(region, self.owner_id)
        self.delete_counter = 1

    def arn(self, region: str, owner_id: str) -> str:
        return (
            f"arn:{get_partition(region)}:ec2:{region}:{owner_id}:prefix-list/{self.id}"
        )

    @property
    def owner_id(self) -> str:
        return self.resource_owner_id or self.ec2_backend.account_id

    @property
    def prefix_list_id(self) -> str:
        return self.id


class ManagedPrefixListBackend:
    def __init__(self) -> None:
        self.managed_prefix_lists: dict[str, ManagedPrefixList] = {}
        self.create_default_pls()

    def create_managed_prefix_list(
        self,
        address_family: Optional[str] = None,
        entry: Optional[list[dict[str, str]]] = None,
        max_entries: Optional[str] = None,
        prefix_list_name: Optional[str] = None,
        tags: Optional[dict[str, str]] = None,
        owner_id: Optional[str] = None,
    ) -> ManagedPrefixList:
        managed_prefix_list = ManagedPrefixList(
            self,
            address_family=address_family,
            entry=entry,
            max_entries=max_entries,
            prefix_list_name=prefix_list_name,
            region=self.region_name,  # type: ignore[attr-defined]
            tags=tags,
            owner_id=owner_id,
        )
        self.managed_prefix_lists[managed_prefix_list.id] = managed_prefix_list
        return managed_prefix_list

    def describe_managed_prefix_lists(
        self, prefix_list_ids: Optional[list[str]] = None, filters: Any = None
    ) -> list[ManagedPrefixList]:
        managed_prefix_lists = list(self.managed_prefix_lists.values())
        attr_pairs = (
            ("owner-id", "owner_id"),
            ("prefix-list-id", "id"),
            ("prefix-list-name", "prefix_list_name"),
        )

        if prefix_list_ids:
            managed_prefix_lists = [
                managed_prefix_list
                for managed_prefix_list in managed_prefix_lists
                if managed_prefix_list.id in prefix_list_ids
            ]

        result = managed_prefix_lists
        if filters:
            result = filter_resources(result, filters, attr_pairs)
            result = describe_tag_filter(filters, result)

        for item in result.copy():
            if not item.delete_counter:
                self.managed_prefix_lists.pop(item.id, None)
                result.remove(item)
            if item.state == "delete-complete":
                item.delete_counter -= 1
        return result

    def get_managed_prefix_list_entries(
        self, prefix_list_id: str
    ) -> Optional[ManagedPrefixList]:
        return self.managed_prefix_lists.get(prefix_list_id)

    def delete_managed_prefix_list(self, prefix_list_id: str) -> ManagedPrefixList:
        managed_prefix_list: ManagedPrefixList = self.managed_prefix_lists.get(
            prefix_list_id
        )  # type: ignore
        managed_prefix_list.state = "delete-complete"
        return managed_prefix_list

    def modify_managed_prefix_list(
        self,
        add_entry: list[dict[str, str]],
        remove_entry: list[dict[str, str]],
        prefix_list_id: Optional[str] = None,
        current_version: Optional[str] = None,
        prefix_list_name: Optional[str] = None,
    ) -> ManagedPrefixList:
        managed_pl: ManagedPrefixList = self.managed_prefix_lists.get(prefix_list_id)  # type: ignore
        managed_pl.prefix_list_name = prefix_list_name
        if remove_entry or add_entry:
            latest_version = managed_pl.entries.get(managed_pl.version)  # type: ignore[arg-type]
            entries = (
                managed_pl.entries.get(current_version, latest_version).copy()  # type: ignore
                if managed_pl.entries
                else []
            )
            for item in entries.copy():
                if item.get("Cidr", "") in [entry["Cidr"] for entry in remove_entry]:
                    entries.remove(item)

            for item in add_entry:
                if item not in entries.copy():
                    entries.append(item)
            managed_pl.version += 1  # type: ignore[operator]
            managed_pl.entries[managed_pl.version] = entries
        managed_pl.state = "modify-complete"
        return managed_pl

    def _create_aws_managed_prefix_list(
        self, name: str, address_family: str, entries: list[dict[str, str]]
    ) -> None:
        managed_prefix_list = self.create_managed_prefix_list(
            address_family=address_family,
            entry=entries,
            prefix_list_name=name,
            owner_id="AWS",
        )
        managed_prefix_list.version = None
        managed_prefix_list.max_entries = None
        self.managed_prefix_lists[managed_prefix_list.id] = managed_prefix_list

    def create_default_pls(self) -> None:
        # See https://docs.aws.amazon.com/vpc/latest/userguide/working-with-aws-managed-prefix-lists.html

        # S3
        self._create_aws_managed_prefix_list(
            name=f"com.amazonaws.{self.region_name}.s3",  # type: ignore[attr-defined]
            address_family="IPv4",
            entries=[
                {"Cidr": "52.216.0.0/15", "Description": "default"},
                {"Cidr": "3.5.0.0/19", "Description": "default"},
                {"Cidr": "54.231.0.0/16", "Description": "default"},
            ],
        )

        # DynamoDB
        self._create_aws_managed_prefix_list(
            name=f"com.amazonaws.{self.region_name}.dynamodb",  # type: ignore[attr-defined]
            address_family="IPv4",
            entries=[
                {"Cidr": "3.218.182.0/24", "Description": "default"},
                {"Cidr": "3.218.180.0/23", "Description": "default"},
                {"Cidr": "52.94.0.0/22", "Description": "default"},
                {"Cidr": "52.119.224.0/20", "Description": "default"},
            ],
        )

        # CloudFront
        self._create_aws_managed_prefix_list(
            name="com.amazonaws.global.cloudfront.origin-facing",
            address_family="IPv4",
            entries=[
                {"Cidr": "13.124.199.0/24", "Description": "default"},
                {"Cidr": "130.176.0.0/18", "Description": "default"},
                {"Cidr": "15.158.0.0/16", "Description": "default"},
                {"Cidr": "18.68.0.0/16", "Description": "default"},
                {"Cidr": "204.246.166.0/24", "Description": "default"},
                {"Cidr": "205.251.218.0/24", "Description": "default"},
                {"Cidr": "3.172.0.0/18", "Description": "default"},
                {"Cidr": "54.239.208.0/21", "Description": "default"},
                {"Cidr": "64.252.64.0/18", "Description": "default"},
                {"Cidr": "70.132.0.0/18", "Description": "default"},
            ],
        )

        # Ground Station
        self._create_aws_managed_prefix_list(
            name="com.amazonaws.global.groundstation",
            address_family="IPv4",
            entries=[{"Cidr": "3.2.16.0/20", "Description": "default"}],
        )

        # VPC Lattice
        self._create_aws_managed_prefix_list(
            name=f"com.amazonaws.{self.region_name}.vpc-lattice",  # type: ignore[attr-defined]
            address_family="IPv4",
            entries=[{"Cidr": "169.254.171.0/24", "Description": "default"}],
        )

        # VPC Lattice ipv6
        self._create_aws_managed_prefix_list(
            name=f"com.amazonaws.{self.region_name}.ipv6.vpc-lattice",  # type: ignore[attr-defined]
            address_family="IPv6",
            entries=[{"Cidr": "fd00:ec2:80::/64", "Description": "default"}],
        )
