import json
import uuid

import boto3
from actions_logging.app_logging import logger
from aws.env_info import get_env_region
from github.env import exit_on_error_and_write_summary, get_required_env_var


def get_distribution_id_by_bucket_name(bucket_name, region="us-east-1"):
    session = boto3.Session()
    cf_client = session.client("cloudfront")

    response = cf_client.list_distributions()
    distributions = response["DistributionList"]["Items"]
    dist_id = None
    for distribution in distributions:
        if "Origins" not in distribution:
            logger.debug(f"distribution without origins: {distribution['id']}")
            continue

        domain_name = distribution["Origins"]["Items"][0]["DomainName"]
        if domain_name == f"{bucket_name}.s3.{region}.amazonaws.com":
            dist_id = distribution["Id"]
        elif domain_name == f"{bucket_name}.s3.amazonaws.com":
            dist_id = distribution["Id"]

    return dist_id


def update_cloudfront_default_root_object(distribution_id, new_root_object):
    try:
        session = boto3.Session()
        cf_client = session.client("cloudfront")

        distribution_config = cf_client.get_distribution_config(Id=distribution_id)
        etag = distribution_config["ETag"]
        config = distribution_config["DistributionConfig"]

        config["DefaultRootObject"] = new_root_object

        cf_client.update_distribution(Id=distribution_id, IfMatch=etag, DistributionConfig=config)

        logger.info("Distribution updated successfully.")
    except Exception as e:
        raise RuntimeError(f"couldn't update cloudfront default root object: {e}")


def create_cloudfront_invalidation(distribution_id):
    try:
        session = boto3.Session()
        cf_client = session.client("cloudfront")

        invalidation_request_id = f"invalidation-{uuid.uuid4()}"
        logger.info_yellow(
            f"Creating CloudFront invalidation for distribution {distribution_id} "
            f"with request ID {invalidation_request_id}."
        )
        res = cf_client.create_invalidation(
            DistributionId=distribution_id,
            InvalidationBatch={"Paths": {"Quantity": 1, "Items": ["/*"]}, "CallerReference": invalidation_request_id},
        )
        res_status = res.get("ResponseMetadata", {}).get("HTTPStatusCode", None)
        if res_status != 201:
            raise RuntimeError(
                f"Failed creating invalidation for request with id {invalidation_request_id}, status code: {res_status}"
            )
        invalidation_id = res.get("Invalidation", {}).get("Id", None)
        if not invalidation_id:
            raise RuntimeError(f"Invalidation ID not found in response for request with id {invalidation_request_id}")
        invalidation_status = res.get("Invalidation", {}).get("Status", None)
        if not invalidation_status or invalidation_status != "InProgress":
            raise RuntimeError(f"Invalidation status is not 'InProgress', got: {invalidation_status}")

        logger.info_green(
            f"Invalidation {invalidation_id} created for distribution {distribution_id} and is 'InProgress'."
        )
    except Exception as e:
        raise RuntimeError(f"An error occurred while trying to invalidate distribution {distribution_id}: {e}")


def get_new_root_object_from_manifest():
    try:
        with open("build/asset-manifest.json", "r") as file:
            manifest = json.load(file)
            return manifest["files"]["main.js"][1:]  # Remove the leading '/' character
    except Exception as e:
        raise RuntimeError(f"An error occurred while reading the manifest: {e}")


def main():
    svc_name = get_required_env_var("SVC_NAME")
    env_name = get_required_env_var("ENV_NAME")
    static_content_bucket_name = get_required_env_var("STATIC_CONTENT_BUCKET_NAME")

    region = get_env_region(env_name)

    if svc_name == "cd-poc-fe":
        logger.info_yellow(f"skipping invalidation since {svc_name} service has no CDN infra")
        exit(0)

    logger.info_yellow(f"static content bucket to look cloudfront for: {static_content_bucket_name}")

    try:
        distribution_id = get_distribution_id_by_bucket_name(static_content_bucket_name, region)

        if not distribution_id:
            raise RuntimeError(f"No CloudFront distribution found for the bucket: {static_content_bucket_name}")

        logger.info_yellow(f"Found distribution ID: {distribution_id}")

        if (
            static_content_bucket_name.startswith("checkpoint-")
            and not static_content_bucket_name.endswith("-us")
            and svc_name == "webclient"
        ):
            logger.info_yellow("This is a webclient service checkpoint deploy need to override default root object")
            new_root_object = get_new_root_object_from_manifest()

            if not new_root_object:
                raise RuntimeError("Failed to get new root object from manifest.")

            logger.info_yellow(f"New root object from manifest: {new_root_object}")
            update_cloudfront_default_root_object(distribution_id, new_root_object)

        create_cloudfront_invalidation(distribution_id)
    except Exception as e:
        exit_on_error_and_write_summary(
            f"Failed invalidating cloudfront for {svc_name} bucket {static_content_bucket_name} in {env_name}, "
            f"please run invalidation manually: {e}"
        )


if __name__ == "__main__":
    main()
