"""End-to-end scenario tests that build complete flows and verify compilation.

These are pytest versions of the key QA test scenarios. Each test builds a
realistic contact flow using the DSL, compiles it (which runs validation
internally), and asserts on the compiled output structure.

The original qa_tests/ scripts are kept for manual DX/gap analysis.
"""

import json
import tempfile
import os

from cxblueprint import Flow, LexV2Bot
from cxblueprint.blocks.contact_actions import (
    TransferContactToQueue,
    UpdateContactCallbackNumber,
)
from cxblueprint.blocks.interactions import CreateCallbackContact
from cxblueprint.blocks.flow_control_actions import Wait, DistributeByPercentage


def _get_action_types(compiled: dict) -> list:
    """Extract sorted list of action types from compiled flow dict."""
    return sorted(a["Type"] for a in compiled["Actions"])


def _action_count(compiled: dict) -> int:
    return len(compiled["Actions"])


class TestBasicIVR:
    """Welcome -> 3-option menu -> department messages -> disconnect."""

    def test_compiles_successfully(self):
        flow = Flow.build("Basic IVR")
        welcome = flow.play_prompt("Welcome to Acme Corp.")
        menu = flow.get_input("Press 1 for Sales, 2 for Support, 3 for Billing.", timeout=8)
        sales = flow.play_prompt("Connecting you to Sales.")
        support = flow.play_prompt("Connecting you to Support.")
        billing = flow.play_prompt("Connecting you to Billing.")
        error_msg = flow.play_prompt("Invalid selection. Goodbye.")
        disconnect = flow.disconnect()

        welcome.then(menu)
        menu.when("1", sales).when("2", support).when("3", billing).otherwise(error_msg)
        menu.on_error("InputTimeLimitExceeded", error_msg)
        menu.on_error("NoMatchingCondition", error_msg)
        menu.on_error("NoMatchingError", error_msg)
        sales.then(disconnect)
        support.then(disconnect)
        billing.then(disconnect)
        error_msg.then(disconnect)

        compiled = flow.compile()
        assert compiled["StartAction"] is not None
        types = _get_action_types(compiled)
        assert types.count("MessageParticipant") >= 4
        assert "GetParticipantInput" in types
        assert "DisconnectParticipant" in types

    def test_end_flow_block(self):
        flow = Flow.build("End Flow Test")
        prompt = flow.play_prompt("Ending now.")
        end = flow.end_flow()
        prompt.then(end)

        compiled = flow.compile()
        types = _get_action_types(compiled)
        assert "EndFlowExecution" in types


class TestLexBotFlow:
    """Welcome -> Lex bot with 3 intents -> intent-specific messages -> disconnect."""

    def test_compiles_successfully(self):
        flow = Flow.build("Lex Bot Flow")
        welcome = flow.play_prompt("Welcome! Let me connect you to our assistant.")
        bot = flow.lex_bot(
            text="How can I help you today?",
            lex_v2_bot=LexV2Bot(alias_arn="arn:aws:lex:us-east-1:123:bot-alias/BOT/ALIAS"),
        )
        order_msg = flow.play_prompt("Let me look up your order.")
        return_msg = flow.play_prompt("I can help with your return.")
        agent_msg = flow.play_prompt("Transferring you to an agent.")
        error_msg = flow.play_prompt("Sorry, I didn't understand.")
        disconnect = flow.disconnect()

        welcome.then(bot)
        bot.on_intent("OrderStatus", order_msg)
        bot.on_intent("ReturnItem", return_msg)
        bot.on_intent("SpeakToAgent", agent_msg)
        bot.otherwise(error_msg)
        bot.on_error("InputTimeLimitExceeded", error_msg)
        bot.on_error("NoMatchingCondition", error_msg)
        bot.on_error("NoMatchingError", error_msg)
        order_msg.then(disconnect)
        return_msg.then(disconnect)
        agent_msg.then(disconnect)
        error_msg.then(disconnect)

        compiled = flow.compile()
        types = _get_action_types(compiled)
        assert "ConnectParticipantWithLexBot" in types


class TestABTesting:
    """Welcome -> 50/50 split -> path A / path B -> shared menu -> disconnect."""

    def test_compiles_successfully(self):
        flow = Flow.build("AB Test Flow")
        welcome = flow.play_prompt("Welcome to our service.")
        ab_split = flow.distribute_by_percentage([50, 50])
        new_greeting = flow.play_prompt("New experience greeting.")
        old_greeting = flow.play_prompt("Classic greeting.")
        shared_menu = flow.get_input("Press 1 for Sales, 2 for Support.", timeout=10)
        sales = flow.play_prompt("Sales team.")
        support = flow.play_prompt("Support team.")
        fallback = flow.play_prompt("Invalid option.")
        disconnect = flow.disconnect()

        welcome.then(ab_split)
        ab_split.branch(0, new_greeting).branch(1, old_greeting)
        new_greeting.then(shared_menu)
        old_greeting.then(shared_menu)
        shared_menu.when("1", sales).when("2", support).otherwise(fallback)
        shared_menu.on_error("InputTimeLimitExceeded", fallback)
        shared_menu.on_error("NoMatchingCondition", fallback)
        shared_menu.on_error("NoMatchingError", fallback)
        sales.then(disconnect)
        support.then(disconnect)
        fallback.then(disconnect)

        compiled = flow.compile()
        types = _get_action_types(compiled)
        assert "DistributeByPercentage" in types


class TestCallbackFlow:
    """Greeting -> ask callback -> collect phone -> update callback number -> create callback -> wait -> confirm -> disconnect."""

    def test_compiles_successfully(self):
        flow = Flow.build("Callback Flow")
        greeting = flow.play_prompt("We are experiencing high call volumes.")
        error_disconnect = flow.disconnect()

        ask = flow.get_input("Press 1 for callback, 2 to stay on the line.", timeout=5)
        greeting.then(ask)

        no_thanks = flow.play_prompt("Please stay on the line.")
        no_disconnect = flow.disconnect()
        no_thanks.then(no_disconnect)

        collect_phone = flow.get_input("Enter your 10-digit phone number.", timeout=15)

        ask.when("1", collect_phone).when("2", no_thanks).otherwise(error_disconnect)
        ask.on_error("InputTimeLimitExceeded", error_disconnect)
        ask.on_error("NoMatchingCondition", error_disconnect)
        ask.on_error("NoMatchingError", error_disconnect)

        update_cb_num = flow.add(UpdateContactCallbackNumber())
        callback = flow.add(CreateCallbackContact())
        wait_block = flow.wait(seconds=30)
        confirm = flow.play_prompt("Your callback has been scheduled.")
        final_disconnect = flow.disconnect()

        collect_phone.otherwise(update_cb_num)
        collect_phone.on_error("InputTimeLimitExceeded", error_disconnect)
        collect_phone.on_error("NoMatchingCondition", error_disconnect)
        collect_phone.on_error("NoMatchingError", error_disconnect)
        update_cb_num.then(callback)
        callback.then(wait_block)
        wait_block.then(confirm)
        confirm.then(final_disconnect)

        compiled = flow.compile()
        types = _get_action_types(compiled)
        assert "CreateCallbackContact" in types
        assert "UpdateContactCallbackNumber" in types
        assert "Wait" in types


class TestQueueTransfer:
    """Welcome -> menu -> set target queue -> transfer to queue -> disconnect."""

    def test_compiles_successfully(self):
        flow = Flow.build("Queue Transfer Flow")
        welcome = flow.play_prompt("Welcome. We will route your call.")
        menu = flow.get_input("Press 1 for Sales, 2 for Support.", timeout=10)
        error_msg = flow.play_prompt("Invalid option.")
        disconnect = flow.disconnect()

        welcome.then(menu)

        sales_transfer = flow.transfer_to_queue()
        support_transfer = flow.transfer_to_queue()

        menu.when("1", sales_transfer).when("2", support_transfer).otherwise(error_msg)
        menu.on_error("InputTimeLimitExceeded", error_msg)
        menu.on_error("NoMatchingCondition", error_msg)
        menu.on_error("NoMatchingError", error_msg)
        sales_transfer.then(disconnect)
        support_transfer.then(disconnect)
        error_msg.then(disconnect)

        compiled = flow.compile()
        types = _get_action_types(compiled)
        assert types.count("TransferContactToQueue") == 2


class TestBusinessHoursLambda:
    """Check hours -> (open) invoke lambda -> compare result -> route -> disconnect."""

    def test_compiles_successfully(self):
        flow = Flow.build("Business Hours + Lambda")
        hours = flow.check_hours(
            hours_of_operation_id="arn:aws:connect:us-east-1:123:instance/i/operating-hours/h"
        )
        closed_msg = flow.play_prompt("We are currently closed.")
        disconnect = flow.disconnect()
        closed_msg.then(disconnect)

        lambda_fn = flow.invoke_lambda(
            function_arn="arn:aws:lambda:us-east-1:123:function:AccountLookup",
            timeout_seconds=8,
        )
        check_result = flow.compare("$.External.lookupResult")
        found_msg = flow.play_prompt("Account found.")
        not_found_msg = flow.play_prompt("Account not found.")
        error_msg = flow.play_prompt("Technical difficulties.")

        hours.when("True", lambda_fn).when("False", closed_msg)
        lambda_fn.then(check_result)
        lambda_fn.on_error("NoMatchingError", error_msg)
        check_result.when("found", found_msg).when("not_found", not_found_msg).otherwise(error_msg)
        found_msg.then(disconnect)
        not_found_msg.then(disconnect)
        error_msg.then(disconnect)

        compiled = flow.compile()
        types = _get_action_types(compiled)
        assert "CheckHoursOfOperation" in types
        assert "InvokeLambdaFunction" in types
        assert "Compare" in types


class TestRecordingControl:
    """Greeting -> pause recording -> collect card -> resume recording -> confirm -> disconnect."""

    def test_compiles_successfully(self):
        flow = Flow.build("PCI Recording Flow")
        greeting = flow.play_prompt("We need your credit card.")
        pause = flow.pause_recording()
        collect = flow.get_input("Enter your card number.", timeout=30)
        resume = flow.resume_recording()
        confirm = flow.play_prompt("Payment received. Thank you.")
        disconnect = flow.disconnect()
        error_disconnect = flow.disconnect()

        greeting.then(pause)
        pause.then(collect)
        collect.otherwise(resume)
        collect.on_error("InputTimeLimitExceeded", error_disconnect)
        collect.on_error("NoMatchingCondition", error_disconnect)
        collect.on_error("NoMatchingError", error_disconnect)
        resume.then(confirm)
        confirm.then(disconnect)

        compiled = flow.compile()
        types = _get_action_types(compiled)
        assert types.count("UpdateContactRecordingBehavior") == 2


class TestDecompileModifyRecompile:
    """Build -> compile to file -> decompile -> verify structure preserved."""

    def test_round_trip(self):
        # Build original flow
        flow = Flow.build("Original Flow")
        welcome = flow.play_prompt("Welcome.")
        disconnect = flow.disconnect()
        welcome.then(disconnect)

        original_compiled = flow.compile()
        original_count = _action_count(original_compiled)

        # Compile to temp file
        with tempfile.NamedTemporaryFile(suffix=".json", delete=False, mode="w") as f:
            f.write(flow.compile_to_json())
            temp_path = f.name

        try:
            # Decompile
            loaded = Flow.decompile(temp_path)
            assert len(loaded.blocks) == original_count

            # Verify block types survived the round trip
            loaded_types = sorted(b.type for b in loaded.blocks)
            assert "MessageParticipant" in loaded_types
            assert "DisconnectParticipant" in loaded_types
        finally:
            os.unlink(temp_path)
