import logging
import time
from iqe_notifications.utils.internal_utils import InternalMessageBuilder
from iqe_notifications.utils.email_alert import NotificationsEmailAlert
from iqe_notifications.utils.email_utils import FindEmailOptions
import pytest

log = logging.getLogger(__name__)

# Centralized retry defaults for inbox polling
INBOX_RETRY_ATTEMPTS_DEFAULT = 6
INBOX_RETRY_TIMEOUT_DEFAULT = 5


class NotificationConfig:
    """Centralized configuration constants for exceeded-utilization-threshold notifications.

    These values must match what the Notifications service expects for routing
    and displaying subscription utilization alerts.
    """

    # Bundle and application identifiers (must match Notifications service registration)
    BUNDLE_NAME = "subscription-services"
    APPLICATION_NAME = "subscriptions"
    EVENT_TYPE = "exceeded-utilization-threshold"

    # Human-readable display name shown in email subjects and UI
    EVENT_TYPE_DISPLAY_NAME = "Subscription threshold exceeded"

    # Default product and metric for testing
    DEFAULT_PRODUCT_ID = "RHEL for x86"
    DEFAULT_METRIC_ID = "Cores"


def send_exceeded_utilization_notification(
    application, product_tag, metric, utilization_percentage
):
    # Setup the notifications alert
    email_alert = NotificationsEmailAlert(
        application,
        event_name=NotificationConfig.EVENT_TYPE_DISPLAY_NAME,
        bundle_name=NotificationConfig.BUNDLE_NAME,
    )
    email_alert.setup()

    # Get current user's org_id and build the message
    current_user_org_id = application.user.identity.org_id
    system_message = (
        exceeded_utilization_message(product_tag, metric, utilization_percentage)
        .with_org_id(current_user_org_id)
        .build()
    )

    # Send the message
    application.notifications.internal_api.internal_resource_v1_post_message(system_message)

    return email_alert


def exceeded_utilization_message(
    product_tag, metric, utilization_percentage, correlation_id: str | None = None
) -> InternalMessageBuilder:
    """
    Base subscription exceeded utilization threshold message.
    Usage example:

        from iqe_notifications.utils.email_alert import (
            NotificationsEmailAlert,
            NotificationsEmailAlertWithoutBgs,
        )

        # Creating Behavior group
        email_alert = NotificationsEmailAlert(
            application, event_name="Exceeded subscription threshold", bundle_name="subscription-services"
        )
        email_alert.setup()

        # Build message
        current_user_org_id = application.user.identity.org_id
        system_message = exceeded_utilization_message(product_id).with_org_id(current_user_org_id).build()

        # Send the message
        application.notifications.internal_api.internal_resource_v1_post_message(system_message)

    """
    # Expected shape for Notifications ingestion:
    # - context: product_id, metric_id
    # - payload: utilization_percentage as string with two decimals
    utilization_str = f"{float(utilization_percentage):.2f}"
    payload: dict[str, str] = {"utilization_percentage": utilization_str}
    if correlation_id:
        payload["iqe_correlation_id"] = correlation_id
    context = {"product_id": product_tag, "metric_id": metric}
    if correlation_id:
        # Add correlation id to context too to increase visibility in Event Log
        context["iqe_correlation_id"] = correlation_id
    return (
        InternalMessageBuilder()
        .with_version("v2.0.0")
        .with_bundle(NotificationConfig.BUNDLE_NAME)
        .with_application(NotificationConfig.APPLICATION_NAME)
        .with_event_type(NotificationConfig.EVENT_TYPE)
        .with_context(context)
        .with_recipients(
            [{"only_admins": "false", "ignore_user_preferences": "false", "users": []}]
        )
        .with_events(
            [
                {
                    "metadata": {},
                    "payload": payload,
                }
            ]
        )
    )


def post_exceeded_utilization_event(
    application,
    product_id: str,
    metric_id: str,
    utilization_percentage: int | float,
    correlation_id: str | None = None,
    org_id: str | None = None,
):
    """Build and post exceeded-utilization event; returns t0 (post time)."""
    t0 = time.time()
    msg = (
        exceeded_utilization_message(
            product_tag=product_id,
            metric=metric_id,
            utilization_percentage=utilization_percentage,
            correlation_id=correlation_id,
        )
        .with_org_id(org_id or application.user.identity.org_id)
        .build()
    )
    application.notifications.internal_api.internal_resource_v1_post_message(msg)
    return t0


def gmail_find_optional(application, options: FindEmailOptions):
    """
    Wrapper around notifications.email.find_email that:
    - Returns the email object or None when nothing is found
    - Raises a clear failure on auth/token errors
    """
    try:
        return application.notifications.email.find_email(options=options)
    except TypeError as e:
        # Library may raise when receive_emails returns None; treat as "no email found"
        log.debug(f"Gmail find_email returned TypeError (likely no emails): {e}")
        return None
    except Exception as exc:
        exc_str = str(exc).lower()
        if "invalid_grant" in exc_str or "unauthorized" in exc_str or "401" in exc_str:
            raise AssertionError(f"Gmail token appears invalid or expired: {exc}")
        raise AssertionError(f"Gmail API error during inbox lookup: {exc}")


def assert_with_meaning(condition: bool, assertion_msg: str, meaning_msg: str):
    """
    Fail with a standardized assertion + meaning message if condition is False.
    """
    if not condition:
        pytest.fail(f"{assertion_msg} | {meaning_msg}")


def find_and_verify_notification_email(
    application,
    subject_token: str,
    required_body_tokens: list[str],
    any_of_body_tokens: list[str] | None = None,
    retry: int = 12,
    retry_timeout: int = 10,
    initial_delay: int = 10,
):
    """
    Find an email and verify tokens are present in the same email body.

    This function searches for emails matching the subject, then verifies that:
    - ALL required_body_tokens are present in the email body
    - At least ONE of any_of_body_tokens is present (if provided)

    This prevents false positives from matching different emails with different tokens.

    Args:
        application: The IQE application fixture
        subject_token: Token to search for in email subject
        required_body_tokens: List of tokens that must ALL be present in the email body
        any_of_body_tokens: List of tokens where at least ONE must be present (e.g., metrics)
        retry: Number of retry attempts (default: 12)
        retry_timeout: Timeout between retries in seconds (default: 10)
        initial_delay: Seconds to wait before first poll, allowing pipeline to process (default: 10)

    Returns:
        EmailEntity: The matched email object

    Raises:
        AssertionError: If no matching email is found or token requirements not met

    Example:
        find_and_verify_notification_email(
            application,
            subject_token="Subscription threshold exceeded",
            required_body_tokens=["1428.57", "ansible-aap-managed", "12345"],
            any_of_body_tokens=["Managed-nodes", "Instance-hours"],  # At least one metric
        )
    """
    # Use the first unique token (utilization percentage) as the primary search token
    primary_token = required_body_tokens[0] if required_body_tokens else ""

    if initial_delay > 0:
        time.sleep(initial_delay)

    for attempt in range(retry):
        opts = FindEmailOptions(
            subject_token=subject_token,
            body_token=primary_token,
            email_amount=50,
            retry=1,  # Single attempt per loop iteration
            retry_timeout=retry_timeout,
        )

        mail = gmail_find_optional(application, opts)

        if mail is not None:
            # Email found - now verify tokens are in the same email body
            email_body = mail.body.lower() if mail.body else ""

            # Check ALL required tokens are present
            missing_tokens = []
            for token in required_body_tokens:
                if token.lower() not in email_body:
                    missing_tokens.append(token)

            # Check at least ONE of any_of tokens is present
            any_of_found = None
            any_of_missing = False
            if any_of_body_tokens:
                for token in any_of_body_tokens:
                    if token.lower() in email_body:
                        any_of_found = token
                        break
                if any_of_found is None:
                    any_of_missing = True

            if not missing_tokens and not any_of_missing:
                if any_of_found:
                    log.info(f"Found metric in email: {any_of_found}")
                return mail  # Success - all requirements met

            if missing_tokens:
                log.warning(
                    f"Attempt {attempt + 1}/{retry}: Email found but missing required tokens: {missing_tokens}"
                )
            if any_of_missing:
                log.warning(
                    f"Attempt {attempt + 1}/{retry}: Email found but missing any of: {any_of_body_tokens}"
                )

        if attempt < retry - 1:
            log.info(
                f"Attempt {attempt + 1}/{retry}: Email not found yet, retrying in {retry_timeout}s..."
            )
            time.sleep(retry_timeout)

    # All retries exhausted
    error_msg = (
        f"No email found matching all requirements. "
        f"Subject token: '{subject_token}', Required body tokens: {required_body_tokens}"
    )
    if any_of_body_tokens:
        error_msg += f", Any of tokens: {any_of_body_tokens}"
    raise AssertionError(error_msg)


def count_notification_emails_matching(
    application,
    subject_token: str,
    required_body_tokens: list[str],
    email_amount: int = 50,
    stop_at_count: int | None = None,
) -> int:
    """Count emails matching subject and body tokens. One pass, no retries.

    stop_at_count: stop once count reaches this (e.g. 2 for dedup: fail fast).
    """
    from googleapiclient.discovery import build

    email_driver = application.notifications.email
    service = build("gmail", "v1", credentials=email_driver.credentials)
    mails = email_driver.receive_emails(
        maxResults=email_amount,
        subject=subject_token,
    )
    if not mails:
        return 0
    count = 0
    for mail in mails:
        content = (
            service.users().messages().get(userId="me", id=mail["id"], format="full").execute()
        )
        payload = content.get("payload", {})
        if "body" not in payload or "data" not in payload["body"]:
            continue
        try:
            body = email_driver.decode_body(content)
        except KeyError:
            continue
        body_lower = body.lower()
        if all(tok.lower() in body_lower for tok in required_body_tokens):
            count += 1
            if stop_at_count is not None and count >= stop_at_count:
                return count
    return count
