import random
import logging
import copy
from enum import Enum
from typing import Dict

import googleads

from .network import Network
from .utils import GoogleServiceAccountClient
from .constants import DISPLAY_NAME_PREFIX


logger = logging.getLogger(__name__)


class AudienceSegmentStatus(Enum):
    ACTIVE = "ACTIVE"
    INACTIVE = "INACTIVE"


class AudienceSegmentsHandler:
    """AudienceSegmentsHandler class to manage GAM audience segments

    NOTE: Test networks are, by default, Ad Manager networks and don't have any features from Ad Manager 360.
          First-party audience segments are only available to Ad Manager 360 networks.
    """

    def __init__(self, application_name, env_tag, gcp_key, version="v202402", **kwargs) -> None:
        self.version = version
        self.network = Network(application_name=application_name, env_tag=env_tag, gcp_key=gcp_key, **kwargs)
        oauth2_client = GoogleServiceAccountClient(gcp_key, googleads.oauth2.GetAPIScope("ad_manager"))
        ad_manager_client = googleads.ad_manager.AdManagerClient(
            oauth2_client,
            application_name=application_name,
            network_code=self.network.network_code,
            **kwargs,
        )
        self.audience_segment_service = ad_manager_client.GetService("AudienceSegmentService", version=self.version)

        if env_tag == "prod":
            self.prefix = DISPLAY_NAME_PREFIX
        else:
            self.prefix = f"[Alloy-{env_tag}]"

        # NOTE: local_cache is used to mock the response for test networks and allow unit/integration tests to pass
        self.local_cache: Dict[int, Dict] = {}

    def create(self, name, description, membership_expiration_in_days=30):
        if name.startswith(self.prefix):
            display_name = name
        else:
            display_name = f"{self.prefix} - {name}"
        # Create an audience segment.
        audience_segment = [
            {
                "xsi_type": "NonRuleBasedFirstPartyAudienceSegment",
                "name": display_name,
                "description": description,
                "membershipExpirationDays": str(membership_expiration_in_days),
            }
        ]
        audience_segments = self.create_audience_segments(audience_segment)

        for created_audience_segment in audience_segments:
            logger.info(
                f"An audience segment with ID `{created_audience_segment['id']}`, "
                f"name `{created_audience_segment['name']}`, "
                f"and type `{created_audience_segment['type']}` "
                "was created."
            )
        return audience_segments[0]["id"]

    def get(self, segment_id):
        # get the current audience segment
        audience_segment_statement = (
            googleads.ad_manager.StatementBuilder(version=self.version)
            .Where("id = :id")
            .WithBindVariable("id", segment_id)
        )
        response = self.get_audience_segments_by_statement(audience_segment_statement.ToStatement())

        if "results" in response and len(response["results"]):
            # The results could also include an INACTIVE key
            audience_segment = response["results"][0]
        else:
            raise ValueError(f"Audience segment with ID {id} does not exist.")
        return audience_segment

    def update(
        self,
        segment_id: int,
        name: str | None = None,
        description: str | None = None,
        status: AudienceSegmentStatus | None = None,
        membership_expiration_in_days: int | None = None,
    ):
        if not any([name, description, status, membership_expiration_in_days]):
            raise ValueError("At least one of name, description, status, membership_expiration_in_days is required.")

        audience_segment = self.get(segment_id)

        # update the audience segment
        if name:
            # Create an audience segment.
            if name.startswith(self.prefix):
                display_name = name
            else:
                display_name = f"{self.prefix} - {name}"

            audience_segment["name"] = display_name

        if description:
            audience_segment["description"] = description

        if status:
            audience_segment["status"] = status.value

        if membership_expiration_in_days:
            audience_segment["membershipExpirationDays"] = str(membership_expiration_in_days)

        audience_segments = self.update_audience_segments([audience_segment])

        for audience_segment in audience_segments:
            logger.info(
                f"An audience segment with ID `{audience_segment['id']}`, "
                f"name `{audience_segment['name']}`, "
                f"and type `{audience_segment['type']}` "
                "was updated."
            )
        return audience_segments[0]["id"]

    # TODO: all the methods below here are introduced just to allow the unit/integration tests to pass
    def create_audience_segments(self, audience_segments):
        if self.network.env_tag == "prod":
            return self.audience_segment_service.createAudienceSegments(audience_segments)

        # we mock the response for test networks
        new_segments = copy.deepcopy(audience_segments)
        for segment in new_segments:
            segment["id"] = random.choice(range(100000000, 999999999))
            segment["status"] = "ACTIVE"
            segment["type"] = "FIRST_PARTY"
            segment["membershipExpirationDays"] = int(segment["membershipExpirationDays"])
            self.local_cache[segment["id"]] = segment
        return new_segments

    def update_audience_segments(self, audience_segments):
        if self.network.env_tag == "prod":
            return self.audience_segment_service.updateAudienceSegments(audience_segments)

        # we mock the response for test networks
        for segment in audience_segments:
            for key, value in segment.items():
                if key == "id":
                    continue
                self.local_cache[segment["id"]][key] = value if key != "membershipExpirationDays" else int(value)
        return copy.deepcopy(audience_segments)

    def get_audience_segments_by_statement(self, statement):
        if self.network.env_tag == "prod":
            return self.audience_segment_service.getAudienceSegmentsByStatement(statement)

        # we mock the response for test networks
        for value in statement["values"]:
            if value["key"] == "id":
                segment_id = value["value"]["value"]
                break

        if segment_id not in self.local_cache:
            # create a dummy segment in the local_cache
            self.local_cache[segment_id] = {
                "id": segment_id,
                "name": f"{self.prefix} - Dummy segment",
                "description": "Dummy segment for test networks",
                "status": "ACTIVE",
                "type": "FIRST_PARTY",
                "membershipExpirationDays": 30,
            }

        response = {
            "totalResultSetSize": 1,
            "startIndex": 1,
            "results": [copy.deepcopy(self.local_cache[segment_id])],
        }
        return response
