# LocalStack Resource Provider Scaffolding v2
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, TypedDict

if TYPE_CHECKING:
    from mypy_boto3_route53 import Route53Client
    from mypy_boto3_route53.type_defs import ResourceRecordSetTypeDef

import localstack.services.cloudformation.provider_utils as util
from localstack.services.cloudformation.resource_provider import (
    OperationStatus,
    ProgressEvent,
    ResourceProvider,
    ResourceRequest,
)


class Route53RecordSetProperties(TypedDict):
    Name: str | None
    Type: str | None
    AliasTarget: AliasTarget | None
    CidrRoutingConfig: CidrRoutingConfig | None
    Comment: str | None
    Failover: str | None
    GeoLocation: GeoLocation | None
    HealthCheckId: str | None
    HostedZoneId: str | None
    HostedZoneName: str | None
    Id: str | None
    MultiValueAnswer: bool | None
    Region: str | None
    ResourceRecords: list[str] | None
    SetIdentifier: str | None
    TTL: str | None
    Weight: int | None


class AliasTarget(TypedDict):
    DNSName: str | None
    HostedZoneId: str | None
    EvaluateTargetHealth: bool | None


class CidrRoutingConfig(TypedDict):
    CollectionId: str | None
    LocationName: str | None


class GeoLocation(TypedDict):
    ContinentCode: str | None
    CountryCode: str | None
    SubdivisionCode: str | None


REPEATED_INVOCATION = "repeated_invocation"


class Route53RecordSetProvider(ResourceProvider[Route53RecordSetProperties]):
    TYPE = "AWS::Route53::RecordSet"  # Autogenerated. Don't change
    SCHEMA = util.get_schema_path(Path(__file__))  # Autogenerated. Don't change

    def create(
        self,
        request: ResourceRequest[Route53RecordSetProperties],
    ) -> ProgressEvent[Route53RecordSetProperties]:
        """
        Create a new resource.

        Primary identifier fields:
          - /properties/Id

        Required properties:
          - Type
          - Name

        Create-only properties:
          - /properties/HostedZoneName
          - /properties/Name
          - /properties/HostedZoneId

        Read-only properties:
          - /properties/Id
        """
        model = request.desired_state
        route53 = request.aws_client_factory.route53

        if not model.get("HostedZoneId"):
            # if only name was provided for hosted zone
            hosted_zone_name = model.get("HostedZoneName")
            hosted_zone_id = self.get_hosted_zone_id_from_name(hosted_zone_name, route53)
            model["HostedZoneId"] = hosted_zone_id

        attrs = self._get_resource_record_set_from_model(model)

        route53.change_resource_record_sets(
            HostedZoneId=model["HostedZoneId"],
            ChangeBatch={
                "Changes": [
                    {
                        "Action": "UPSERT",
                        "ResourceRecordSet": attrs,
                    },
                ]
            },
        )
        # TODO: not 100% sure this behaves the same between alias and non-alias records
        model["Id"] = model["Name"]

        return ProgressEvent(
            status=OperationStatus.SUCCESS,
            resource_model=model,
        )

    def get_hosted_zone_id_from_name(self, hosted_zone_name: str, client: Route53Client):
        if not hosted_zone_name:
            raise Exception("Either HostedZoneId or HostedZoneName must be present.")

        zones = client.list_hosted_zones_by_name(DNSName=hosted_zone_name)["HostedZones"]
        if len(zones) != 1:
            raise Exception(f"Ambiguous HostedZoneName {hosted_zone_name} provided.")

        hosted_zone_id = zones[0]["Id"]
        return hosted_zone_id

    def read(
        self,
        request: ResourceRequest[Route53RecordSetProperties],
    ) -> ProgressEvent[Route53RecordSetProperties]:
        """
        Fetch resource information


        """
        raise NotImplementedError

    def delete(
        self,
        request: ResourceRequest[Route53RecordSetProperties],
    ) -> ProgressEvent[Route53RecordSetProperties]:
        """
        Delete a resource


        """
        model = request.previous_state
        route53 = request.aws_client_factory.route53

        resource_record_set = self._get_resource_record_set_from_model(model)
        route53.change_resource_record_sets(
            HostedZoneId=model["HostedZoneId"],
            ChangeBatch={
                "Changes": [
                    {
                        "Action": "DELETE",
                        "ResourceRecordSet": resource_record_set,
                    },
                ]
            },
        )
        return ProgressEvent(
            status=OperationStatus.SUCCESS,
            resource_model=model,
        )

    def update(
        self,
        request: ResourceRequest[Route53RecordSetProperties],
    ) -> ProgressEvent[Route53RecordSetProperties]:
        """
        Update a resource


        """
        model = request.desired_state
        prev_model = request.previous_state

        assert request.previous_state is not None

        route53 = request.aws_client_factory.route53
        changes = []

        if model.get("SetIdentifier") != prev_model.get("SetIdentifier"):
            prev_rrset = self._get_resource_record_set_from_model(prev_model)
            changes.append(
                {
                    "Action": "DELETE",
                    "ResourceRecordSet": prev_rrset,
                }
            )

        updated_rrset = self._get_resource_record_set_from_model(model)
        changes.append(
            {
                "Action": "UPSERT",
                "ResourceRecordSet": updated_rrset,
            }
        )

        route53.change_resource_record_sets(
            HostedZoneId=model["HostedZoneId"],
            ChangeBatch={"Changes": changes},
        )
        model["Id"] = model["Name"]

        return ProgressEvent(
            status=OperationStatus.SUCCESS,
            resource_model=model,
        )

    @staticmethod
    def _get_resource_record_set_from_model(
        model: Route53RecordSetProperties,
    ) -> ResourceRecordSetTypeDef:
        attr_names = [
            "Name",
            "Type",
            "SetIdentifier",
            "Weight",
            "Region",
            "GeoLocation",
            "Failover",
            "MultiValueAnswer",
            "TTL",
            "ResourceRecords",
            "AliasTarget",
            "HealthCheckId",
        ]
        attrs = util.select_attributes(model, attr_names)

        if "AliasTarget" in attrs:
            # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-recordset-aliastarget.html
            if "EvaluateTargetHealth" not in attrs["AliasTarget"]:
                attrs["AliasTarget"]["EvaluateTargetHealth"] = False
        else:
            # TODO: CNAME & SOA only allow 1 record type. should we check that here?
            attrs["ResourceRecords"] = [{"Value": record} for record in attrs["ResourceRecords"]]

        if "TTL" in attrs:
            if isinstance(attrs["TTL"], str):
                attrs["TTL"] = int(attrs["TTL"])

        return attrs
