"""
E2E tests for notification flow (stage notification receipt and deduplication).

Implements:
- stage-notification-receipt-TC001: Opted-in org with over-threshold usage receives
  one notification per product-metric per calendar month after tally sync.
- stage-notification-deduplication-TC001: No second notification for the same
  product-metric in the same month; sanity check via a new over-usage condition.

"""

import datetime
import logging
import time
import uuid

import dateutil
import pytest

from iqe_rhsm_subscriptions.rhsmlib import config_parser
from iqe_rhsm_subscriptions.utils.datetime_utils import get_monthly_range
from iqe_rhsm_subscriptions.conftest import create_org_with_ethel, opt_in_to_swatch_with_retry
from iqe_rhsm_subscriptions.tests.component.swatch_utilization.helpers import (
    enable_notifications_with_retry,
)
from iqe_rhsm_subscriptions.utils.notifications_utils import (
    NotificationConfig,
    count_notification_emails_matching,
    find_and_verify_notification_email,
)

log = logging.getLogger(__name__)

BUNDLE_NAME = NotificationConfig.BUNDLE_NAME

# Shared timing for notification inbox polling (aligns dedup wait with first-email wait)
_NOTIFICATION_RETRY = 6
_NOTIFICATION_RETRY_TIMEOUT = 10
_NOTIFICATION_INITIAL_DELAY = 30


@pytest.mark.post_stage_deploy
@pytest.mark.e2e
@pytest.mark.notifications
def test_stage_notification_receipt_TC001(application, notification_test_email):
    """stage-notification-receipt-TC001: Org receives notification when usage exceeds threshold.

    Flow matches the working patch: create org, opt-in, enable notifications,
    create contract, inject usage, sync **hourly** tally, verify email.

    metadata:
        assignee: tlencion
        importance: critical
        requirements: utilization
        negative: false
        test_steps:
            1. Create org with Ethel using the configured test email
            2. Opt-in to SWATCH for the organization
            3. Enable notifications for the org (behavior groups + instant preference)
            4. Create ansible-aap-managed contract and inject over-threshold usage
            5. Sync hourly tally
            6. Verify exceeded-utilization email is received in Gmail inbox
        expected_results:
            1. Organization is created with username, password, and org_id
            2. SWATCH opt-in is successful
            3. Behavior groups and instant notification preference are enabled
            4. Contract and usage data are created successfully
            5. Tally sync completes
            6. Email notification is received with correct product, org, metric, and utilization
    """
    org_info = create_org_with_ethel(
        application=application,
        email=notification_test_email,
        username=None,
        password=None,
        accept_terms=True,
    )
    assert org_info["username"], "Username should be generated"
    assert org_info["password"], "Password should be generated"
    assert org_info["org_id"], "Org ID should be available"

    username = org_info["username"]
    password = org_info["password"]
    org_id = org_info["org_id"]
    log.debug(f"Org created: {username} (org_id={org_id})")

    opt_in_result = opt_in_to_swatch_with_retry(
        application=application,
        username=username,
        password=password,
    )
    assert opt_in_result["opt_in_complete"] is True, "SWATCH opt-in should be successful"
    log.info("SWATCH opt-in complete")

    result = enable_notifications_with_retry(
        application=application,
        username=username,
        password=password,
        event_name="Subscription threshold exceeded",
        bundle_name=BUNDLE_NAME,
        enable_instant_preference=True,
    )
    assert result["email_alert"] is not None, "Email alert should be created"
    assert result["preference_enabled"] is True, "Instant preference should be enabled"
    log.info(f"Notifications configured: behavior_group={result['email_alert'].behavior_group._id}")

    product_id = "ansible-aap-managed"
    billing_provider = "aws"
    contract_value = 100
    base_usage = 110 + (uuid.uuid4().int % 41)
    decimal_part = (uuid.uuid4().int % 100) / 100.0
    usage_value = base_usage + decimal_part
    expected_utilization_pct = (usage_value / contract_value) * 100.0
    expected_utilization_str = f"{expected_utilization_pct:.2f}"

    metric_id = "Managed-nodes"
    metric_dict = {metric_id.replace("-", "_").lower(): contract_value}

    start_date, end_date = get_monthly_range()
    customer_id = str(uuid.uuid4())

    contract = application.rhsm_subscriptions.rhsm_internal_api.contracts.add_contract(
        org_id=org_id,
        product_id=product_id,
        billing_provider=billing_provider,
        customer_id=customer_id,
        start_date=start_date,
        end_date=end_date,
        **metric_dict,
    )
    assert contract, f"Contract data either blank or returned False in response: {contract}"
    assert contract["status"]["status"] == "SUCCESS", (
        f"Contract creation not successful: {contract['status']}"
    )

    billing_account_id = contract["contract"]["billing_account_id"]
    log.info(
        f"Contract created: product={product_id}, capacity={contract_value}, "
        f"billing_account={billing_account_id}"
    )

    event_start_time = dateutil.parser.parse(start_date) + datetime.timedelta(days=1)

    control_node, managed_nodes = (
        application.rhsm_subscriptions.rhsm_internal_api.tally.create_ansible_data(
            org_id=org_id,
            hours=1,
            start_time=event_start_time,
            billing_provider=billing_provider,
            billing_account_id=billing_account_id,
            **{metric_id.lower(): usage_value},
        )
    )
    cluster_uuid = control_node["instance_id"]
    log.info(
        f"Usage injected: {usage_value:.2f} units (capacity={contract_value}) -> "
        f"{expected_utilization_str}% utilization, cluster={cluster_uuid}"
    )

    application.rhsm_subscriptions.rhsm_internal_api.tally.sync_tally(
        org_id=org_id,
        hourly=True,
        start=start_date,
        end=end_date,
    )
    log.info("Tally sync complete")

    required_body_tokens = [
        expected_utilization_str,
        product_id,
        org_id,
        metric_id,
    ]
    find_and_verify_notification_email(
        application,
        subject_token="Subscription threshold exceeded",
        required_body_tokens=required_body_tokens,
        retry=_NOTIFICATION_RETRY,
        retry_timeout=_NOTIFICATION_RETRY_TIMEOUT,
        initial_delay=_NOTIFICATION_INITIAL_DELAY,
    )
    log.info(
        f"Email verified: org={org_id}, product={product_id}, metric={metric_id}, "
        f"utilization={expected_utilization_str}%"
    )


@pytest.mark.post_stage_deploy
@pytest.mark.e2e
@pytest.mark.notifications
def test_stage_notification_deduplication_TC001(application, notification_test_email):
    """stage-notification-deduplication-TC001: No duplicate for same product-metric in month.

    Same flow as receipt test for first notification; then trigger tally sync again
    (hourly=True) and verify no second email for same over-usage. Sanity: new over-usage
    (Instance-hours) and verify that notification is received.

    metadata:
        assignee: tlencion
        importance: critical
        requirements: utilization
        negative: false
        test_steps:
            1. Create org, opt-in, enable notifications (same as receipt test)
            2. Create ansible-aap-managed contract and inject over-threshold Managed-nodes usage
            3. Sync tally and verify first email received
            4. Sync tally again (same over-usage) and verify no duplicate email
            5. Add second contract with Instance-hours over-threshold usage
            6. Sync tally and verify Instance-hours notification is received
        expected_results:
            1. Org and notifications are configured
            2. First notification for Managed-nodes over-usage is received
            3. First email is received
            4. Exactly one email for Managed-nodes (no duplicate)
            5. Second contract and Instance-hours usage are created
            6. Instance-hours over-threshold notification is received
    """
    org_info = create_org_with_ethel(
        application=application,
        email=notification_test_email,
        username=None,
        password=None,
        accept_terms=True,
    )
    assert org_info["username"] and org_info["password"] and org_info["org_id"]
    username = org_info["username"]
    password = org_info["password"]
    org_id = org_info["org_id"]

    opt_in_result = opt_in_to_swatch_with_retry(
        application=application, username=username, password=password
    )
    assert opt_in_result["opt_in_complete"] is True

    result = enable_notifications_with_retry(
        application=application,
        username=username,
        password=password,
        event_name="Subscription threshold exceeded",
        bundle_name=BUNDLE_NAME,
        enable_instant_preference=True,
    )
    assert result["email_alert"] is not None and result["preference_enabled"] is True

    product_id = "ansible-aap-managed"
    metric_id = "Managed-nodes"
    contract_value = 100
    usage_value_1 = 115 + (uuid.uuid4().int % 100) / 100.0
    expected_utilization_str_1 = f"{(usage_value_1 / contract_value) * 100.0:.2f}"

    start_date, end_date = get_monthly_range()
    customer_id = str(uuid.uuid4())
    metric_dict = {metric_id.replace("-", "_").lower(): contract_value}

    contract = application.rhsm_subscriptions.rhsm_internal_api.contracts.add_contract(
        org_id=org_id,
        product_id=product_id,
        billing_provider="aws",
        customer_id=customer_id,
        start_date=start_date,
        end_date=end_date,
        **metric_dict,
    )
    assert contract and contract["status"]["status"] == "SUCCESS"
    billing_account_id = contract["contract"]["billing_account_id"]
    event_start_time = dateutil.parser.parse(start_date) + datetime.timedelta(days=1)

    application.rhsm_subscriptions.rhsm_internal_api.tally.create_ansible_data(
        org_id=org_id,
        hours=1,
        start_time=event_start_time,
        billing_provider="aws",
        billing_account_id=billing_account_id,
        **{metric_id.lower(): usage_value_1},
    )

    application.rhsm_subscriptions.rhsm_internal_api.tally.sync_tally(
        org_id=org_id,
        hourly=True,
        start=start_date,
        end=end_date,
    )
    find_and_verify_notification_email(
        application,
        subject_token="Subscription threshold exceeded",
        required_body_tokens=[expected_utilization_str_1, product_id, org_id, metric_id],
        retry=_NOTIFICATION_RETRY,
        retry_timeout=_NOTIFICATION_RETRY_TIMEOUT,
        initial_delay=_NOTIFICATION_INITIAL_DELAY,
    )

    application.rhsm_subscriptions.rhsm_internal_api.tally.sync_tally(
        org_id=org_id,
        hourly=True,
        start=start_date,
        end=end_date,
    )
    # Assess absence of a new email: wait same max duration as first check (initial_delay + retries).
    _dedup_wait = (
        _NOTIFICATION_INITIAL_DELAY + (_NOTIFICATION_RETRY - 1) * _NOTIFICATION_RETRY_TIMEOUT
    )
    time.sleep(_dedup_wait)

    count_first = count_notification_emails_matching(
        application,
        subject_token="Subscription threshold exceeded",
        required_body_tokens=[
            expected_utilization_str_1,
            product_id,
            org_id,
            metric_id,
        ],
        email_amount=10,
        stop_at_count=2,
    )
    assert count_first == 1, (
        f"Expected exactly one email for first over-usage (deduplication); got {count_first}."
    )

    # Second over-usage: same product (ansible-aap-managed), different metric (Instance-hours).
    # Contract must include both dimensions; only Instance-hours over threshold.
    # create_metrics_for_contract (dimension products) looks up kwargs by normalized key:
    # metric_id.replace("-", "_").lower() (e.g. instance_hours, managed_nodes).
    product_id_2 = "ansible-aap-managed"
    metric_id_2 = "Instance-hours"
    contract_value_2 = 50
    usage_value_2 = 60 + (uuid.uuid4().int % 100) / 100.0
    expected_utilization_str_2 = f"{(usage_value_2 / contract_value_2) * 100.0:.2f}"
    customer_id_2 = str(uuid.uuid4())
    metrics_for_product = config_parser.get_swatch_metric_id_from_tag_metrics(
        product_id=product_id_2
    )
    normalized = {m: m.replace("-", "_").lower() for m in metrics_for_product}
    metric_dict_2 = {normalized[m]: 100 for m in metrics_for_product}
    metric_dict_2[normalized[metric_id_2]] = contract_value_2

    contract2 = application.rhsm_subscriptions.rhsm_internal_api.contracts.add_contract(
        org_id=org_id,
        product_id=product_id_2,
        billing_provider="aws",
        customer_id=customer_id_2,
        start_date=start_date,
        end_date=end_date,
        **metric_dict_2,
    )
    assert contract2 and contract2["status"]["status"] == "SUCCESS"
    billing_account_id_2 = contract2["contract"]["billing_account_id"]
    event_start_2 = dateutil.parser.parse(start_date) + datetime.timedelta(days=2)

    # create_ansible_data expects hyphenated lowercase keys (managed-nodes, instance-hours)
    application.rhsm_subscriptions.rhsm_internal_api.tally.create_ansible_data(
        org_id=org_id,
        hours=1,
        start_time=event_start_2,
        billing_provider="aws",
        billing_account_id=billing_account_id_2,
        **{"managed-nodes": 1, "instance-hours": usage_value_2},
    )

    application.rhsm_subscriptions.rhsm_internal_api.tally.sync_tally(
        org_id=org_id,
        hourly=True,
        start=start_date,
        end=end_date,
    )

    # Assess third email (new over-usage, Instance-hours); same timing as first.
    find_and_verify_notification_email(
        application,
        subject_token="Subscription threshold exceeded",
        required_body_tokens=[
            expected_utilization_str_2,
            product_id_2,
            org_id,
            metric_id_2,
        ],
        retry=_NOTIFICATION_RETRY,
        retry_timeout=_NOTIFICATION_RETRY_TIMEOUT,
        initial_delay=_NOTIFICATION_INITIAL_DELAY,
    )
