#!/usr/bin/env python3
import http.server
import socketserver
import webbrowser
import threading
import requests
import urllib.parse
import json
import time
import os
import base64
import sys
import argparse
import logging
from datetime import datetime, timedelta
import uuid
from rich.console import Console
from rich.json import JSON
from rich.progress import Progress, SpinnerColumn, TextColumn, TimeRemainingColumn
import concurrent.futures

# Setup a common formatter
log_formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")

# Setup logging for the test script
handler = logging.StreamHandler()
handler.setFormatter(log_formatter)

logging.basicConfig(level=logging.DEBUG, handlers=[handler])

# Create Rich console for pretty output
console = Console()

# Reset lambda function's global variables since we're importing it directly
# This ensures each test run starts with a fresh state
import lambda_function

# Set cold start time to current time to avoid confusing time calculations in tests
lambda_function._cold_start_time = time.time()
lambda_function._initialization_time = 0.0  # Set to zero for tests

# Configure lambda function's logger to use our configuration
lambda_logger_handler = logging.StreamHandler()
lambda_logger_handler.setFormatter(log_formatter)

lambda_function.logger.handlers = []  # Clear existing handlers
lambda_function.logger.addHandler(lambda_logger_handler)
lambda_function.logger.setLevel(logging.DEBUG)
lambda_function.logger.propagate = False  # Prevent propagation to avoid duplicate logs

# OAuth Configuration - Replace with your own values
CLIENT_ID = "suno--37hs8TxBwOUKtaOJVzZrQ"
CLIENT_SECRET = "H_0jz1Q4LzYLWmghu7skTFgzCDgcnxeOtkGc1zD4UBM"
REDIRECT_URI = "http://localhost:3001/oauth_callback"
AUTH_ENDPOINT = "https://studio-api.prod.suno.com/api/v2/external/oauth/authorize/"
TOKEN_ENDPOINT = "https://studio-api.prod.suno.com/api/v2/external/oauth/token/"

# Path to request examples
REQUEST_EXAMPLES_DIR = "request_examples"

# Test settings
# Set this to match lambda_function.TARGET_TOTAL_TIME_SECONDS
TEST_TARGET_TIME = 5.0


class OAuthCallbackHandler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path.startswith("/oauth_callback"):
            query = urllib.parse.urlparse(self.path).query
            params = urllib.parse.parse_qs(query)

            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()

            if "code" in params:
                self.server.auth_code = params["code"][0]
                response = """
                <html><body>
                <h1>Authorization Successful!</h1>
                <p>You can now close this window and return to the terminal.</p>
                </body></html>
                """
            else:
                error = params.get("error", ["Unknown error"])[0]
                response = f"""
                <html><body>
                <h1>Authorization Failed</h1>
                <p>Error: {error}</p>
                </body></html>
                """

            self.wfile.write(response.encode())
            print("\nRedirect received. You can close the browser window.")
            return

        return super().do_GET()

    def log_message(self, format, *args):
        # Suppress server logs
        return


def start_oauth_server():
    """Start the OAuth callback server and return the server instance"""

    class CustomTCPServer(socketserver.TCPServer):
        allow_reuse_address = True
        auth_code = None

    server = CustomTCPServer(("", 3001), OAuthCallbackHandler)
    server.auth_code = None

    server_thread = threading.Thread(target=server.serve_forever)
    server_thread.daemon = True
    server_thread.start()
    print("Server started on port 3001")

    return server


def get_authorization_url():
    """Generate the authorization URL"""
    params = {
        "client_id": CLIENT_ID,
        "response_type": "code",
        "redirect_uri": REDIRECT_URI,
        "scope": "read_profile generate_music read_music",
        "state": f"test-{int(time.time())}",
    }
    return f"{AUTH_ENDPOINT}?{urllib.parse.urlencode(params)}"


def exchange_code_for_token(code):
    """Exchange the authorization code for an access token"""
    data = {
        "grant_type": "authorization_code",
        "code": code,
        "redirect_uri": REDIRECT_URI,
        "client_id": CLIENT_ID,
    }

    headers = {
        "Content-Type": "application/x-www-form-urlencoded",
        "Authorization": f"Basic {base64.b64encode(f'{CLIENT_ID}:{CLIENT_SECRET}'.encode()).decode()}",
        "x-auth-type": "oauth",  # Required by Suno API
    }

    response = requests.post(TOKEN_ENDPOINT, data=data, headers=headers)
    response.raise_for_status()
    return response.json()


def get_oauth_token():
    """Complete the OAuth flow and return an access token"""
    server = start_oauth_server()

    try:
        auth_url = get_authorization_url()
        print("\nAuthorization URL:")
        print(auth_url)

        webbrowser.open(auth_url)

        print("Waiting for authorization...")
        wait_time = 0
        while server.auth_code is None and wait_time < 300:
            time.sleep(1)
            wait_time += 1

        if server.auth_code:
            print("Authorization code received, exchanging for tokens...")
            token_data = exchange_code_for_token(server.auth_code)

            if token_data and "access_token" in token_data:
                print("Access token obtained successfully!")
                return token_data["access_token"]
            else:
                print("Failed to exchange authorization code for tokens.")
                return None
        else:
            print("Timed out waiting for authorization.")
            return None
    finally:
        # Improve server shutdown to prevent hanging
        try:
            # Set a timeout for the shutdown
            shutdown_thread = threading.Thread(target=server.shutdown)
            shutdown_thread.daemon = True
            shutdown_thread.start()

            # Wait for shutdown with timeout
            shutdown_thread.join(timeout=2.0)

            # Continue even if timeout occurs
            if shutdown_thread.is_alive():
                print("Server shutdown timed out, but continuing...")
            else:
                server.server_close()
                print("Server stopped")
        except Exception as e:
            print(f"Error during server shutdown: {e}")
            print("Continuing despite server shutdown issue...")


def create_get_next_item_request(
    access_token, clip_id, queue_id, is_user_initiated=True
):
    """Create a sample GetNextItem request with configurable parameters."""
    request = {
        "header": {
            "messageId": f"test-{int(time.time())}",
            "namespace": "Alexa.Audio.PlayQueue",
            "name": "GetNextItem",
            "payloadVersion": "1.0",
        },
        "payload": {
            "requestContext": {
                "user": {
                    "id": "amzn1.ask.account.TEST_USER_GENERATE",
                    "accessToken": access_token,
                },
                "location": {"originatingLocale": "en-US"},
            },
            "currentItemReference": {
                "id": clip_id,
                "queueId": queue_id,
                "contentId": clip_id,
            },
            "isUserInitiated": is_user_initiated,
        },
    }
    return request


def create_get_previous_item_request(access_token, clip_id, queue_id, is_user_initiated=True):
    """Create a sample GetPreviousItem request with clip_id"""
    return {
        "header": {
            "messageId": f"test-{int(time.time())}",
            "namespace": "Alexa.Audio.PlayQueue",
            "name": "GetPreviousItem",
            "payloadVersion": "1.0",
        },
        "payload": {
            "requestContext": {
                "user": {
                    "id": "amzn1.ask.account.TEST_USER_GENERATE",
                    "accessToken": access_token,
                },
                "location": {
                    "originatingLocale": "en-US"
                }
            },
            "currentItemReference": {
                "id": clip_id,
                "queueId": queue_id,
                "contentId": clip_id,
            },
            "isUserInitiated": is_user_initiated,
        },
    }


def create_gpc_request(access_token, query="Create a song about a cat"):
    """Create a sample GetPlayableContent request with query AND generate action"""
    return {
        "header": {
            "messageId": f"test-{int(time.time())}",
            "namespace": "Alexa.Media.Search",
            "name": "GetPlayableContent",
            "payloadVersion": "3.0",
        },
        "payload": {
            "requestContext": {
                "user": {
                    # Add a default user ID for completeness, although not strictly used in generation yet
                    "id": "amzn1.ask.account.TEST_USER_GENERATE",
                    "accessToken": access_token,
                },
                # Add location and advertising stubs as seen in example
                "location": {"originatingLocale": "en-US", "countryCode": "US"},
                "advertising": {
                    "limitAdTracking": True,
                    "advertisingId": "00000000-0000-0000-0000-000000000000",
                },
            },
            "rankedSelectionCriteria": [
                {
                    "id": "NL_QUERY_CRITERIA",  # Match example ID
                    "type": "NL_QUERY",
                    "query": query,
                },
                # Add the dummy ATTRIBUTES criteria from the example for realism
                {
                    "type": "ATTRIBUTES",
                    "id": "MULTI_ATTR_CRITERIA",
                    "attributes": [
                        {
                            "type": "MEDIA_TYPE",
                            "id": "U09ORw==",  # Base64 for "SONG"
                            "rawValue": "SONG",
                            "value": "TRACK",
                        }
                    ],
                    "completeness": {"score": 0.0, "bin": "LOW"},
                    "query": None,
                },
            ],
            # Add the crucial action field
            "action": {"type": "GENERATE_CONTENT"},
            # Add filters stub from example
            "filters": {"explicitLanguageAllowed": True},
        },
    }


def create_simple_gpc_request(access_token):
    """Create a simplified GetPlayableContent request without query"""
    return {
        "header": {
            "messageId": f"test-{int(time.time())}",
            "namespace": "Alexa.Media.Search",
            "name": "GetPlayableContent",
            "payloadVersion": "3.0",
        },
        "payload": {
            "requestContext": {"user": {"accessToken": access_token}},
            "filters": {"explicitLanguageAllowed": True},
            # Add mandatory rankedSelectionCriteria for GPC 3.0
            "rankedSelectionCriteria": [
                {
                    "type": "ATTRIBUTES",
                    "id": "criteria-simple-track-id-placeholder",
                    "completeness": {"bin": "HIGH", "score": 1.0},
                    "attributes": [
                        {
                            "type": "TRACK",
                            "id": "attr-track-placeholder",
                            "resolvedEntities": [
                                {
                                    # Replace with an actual track ID known to your system/Suno if needed for a more realistic test
                                    "entityId": "fb4afa08-e64b-41ef-8bac-738d23a8076b",
                                }
                            ],
                        }
                    ],
                    "query": None,  # No complex query needed for simple ID lookup
                }
                # Add other simple criteria if needed for different test cases
            ],
        },
    }


def create_initiate_request(
    access_token, content_id, user_id="amzn1.ask.account.DEFAULT_USER"
):
    """Create a sample Initiate request matching the format provided"""
    return {
        "header": {
            "namespace": "Alexa.Media.Playback",
            "name": "Initiate",
            "messageId": str(uuid.uuid4()),
            "payloadVersion": "1.0",  # Updated to match lambda expectations
        },
        "payload": {
            "requestContext": {
                "user": {"id": user_id, "accessToken": access_token},
                "location": {"originatingLocale": "en-US"},
            },
            "filters": {"explicitLanguageAllowed": True},
            "contentId": content_id,
            "playbackModes": {
                "shuffle": False,
                "loop": False,
                "repeat": {"status": "OFF"},
            },
        },
    }


def create_set_loop_request(access_token, clip_id, queue_id, enable=True):
    """Create a SetLoop request."""
    return {
        "header": {
            "messageId": f"test-set-loop-{uuid.uuid4()}",
            "namespace": "Alexa.Media.PlayQueue",
            "name": "SetLoop",
            "payloadVersion": "1.0"
        },
        "payload": {
            "requestContext": {
                "user": {
                    "id": "amzn1.ask.account.TEST_USER_GENERATE",
                    "accessToken": access_token
                }
            },
            "enable": enable,
            "currentItemReference": {
                "namespace": "Alexa.Audio.PlayQueue",
                "name": "item",
                "value": {
                    "contentId": clip_id,
                    "queueId": queue_id,
                    "id": clip_id,
                    "content": {
                        "id": clip_id,
                        "metadataType": "TRACK"
                    }
                }
            }
        }
    }


def create_set_repeat_request(access_token, clip_id, queue_id, status="ON"):
    """Create a SetRepeat request."""
    return {
        "header": {
            "messageId": f"test-set-repeat-{uuid.uuid4()}",
            "namespace": "Alexa.Media.PlayQueue",
            "name": "SetRepeat",
            "payloadVersion": "1.0"
        },
        "payload": {
            "requestContext": {
                "user": {
                    "id": "amzn1.ask.account.TEST_USER_GENERATE",
                    "accessToken": access_token
                }
            },
            "currentItemReference": {
                "namespace": "Alexa.Audio.PlayQueue",
                "name": "item",
                "value": {
                    "contentId": clip_id,
                    "queueId": queue_id,
                    "id": clip_id,
                    "content": {
                        "id": clip_id,
                        "metadataType": "TRACK"
                    }
                }
            },
            "mode": {
                "status": status
            }
        }
    }


def load_request_from_file(filename, access_token=None):
    """Load a request from a JSON file and inject access token if provided"""
    full_path = os.path.join(REQUEST_EXAMPLES_DIR, filename)

    try:
        with open(full_path, "r") as f:
            request = json.load(f)

        # Inject access token if provided
        if (
            access_token
            and "payload" in request
            and "requestContext" in request["payload"]
            and "user" in request["payload"]["requestContext"]
        ):
            request["payload"]["requestContext"]["user"]["accessToken"] = access_token

        return request
    except Exception as e:
        print(f"Error loading request from {filename}: {e}")
        return None


def list_available_requests():
    """List all available request examples in the request_examples directory"""
    if not os.path.exists(REQUEST_EXAMPLES_DIR):
        print(f"Request examples directory '{REQUEST_EXAMPLES_DIR}' does not exist.")
        return []

    files = [f for f in os.listdir(REQUEST_EXAMPLES_DIR) if f.endswith(".json")]
    return files


def save_request_to_file(request):
    """Save request JSON to a file for AWS Lambda console testing"""
    # Create directory if it doesn't exist
    os.makedirs("aws_console_requests", exist_ok=True)

    # Get request type and current timestamp for filename
    request_type = request.get("header", {}).get("name", "unknown")
    timestamp = int(time.time())

    # Create filename with request type and timestamp
    filename = f"aws_console_requests/{request_type}_{timestamp}.json"

    # Save request to file
    with open(filename, "w") as f:
        json.dump(request, f, indent=2)

    print(f"\nRequest saved to {filename} for AWS console testing")
    return filename


# Utility function for printing JSON consistently
def print_json(data, title=None):
    """Print JSON data using Rich for better formatting"""
    if title:
        console.print(f"\n[bold cyan]{title}[/bold cyan]")

    if isinstance(data, str):
        # If data is already a JSON string
        console.print(JSON(data))
    else:
        # Convert data to a JSON string
        console.print(JSON(json.dumps(data)))


def test_lambda(request, mock_context=None, print_raw_request=False, function_url=None):
    """Test the lambda function with a request, either locally or via a URL."""

    # Save request to file for AWS console testing
    save_request_to_file(request)

    # Print the raw request JSON for AWS console testing
    if print_raw_request:
        print("\nRaw request JSON (copy this for AWS Lambda console testing):")
        print_json(request)

    test_target_description = (
        f"against URL: {function_url}" if function_url else "locally"
    )
    print(
        f"\nTesting lambda {test_target_description} with request type: {request['header']['name']}"
    )

    # Create a logging-safe copy of the payload with the token truncated/hidden only for display
    display_payload = json.loads(json.dumps(request["payload"]))
    if (
        "requestContext" in display_payload
        and "user" in display_payload["requestContext"]
        and "accessToken" in display_payload["requestContext"]["user"]
    ):
        token = display_payload["requestContext"]["user"]["accessToken"]
        display_payload["requestContext"]["user"]["accessToken"] = (
            f"{token[:10]}...{token[-5:]}" if len(token) > 15 else "[hidden]"
        )

    print_json(display_payload, "Request payload")

    # Start timing the request/call
    start_time = time.time()
    response = None
    error_message = None

    if function_url:
        # Test against the provided URL
        try:
            headers = {"Content-Type": "application/json"}
            http_response = requests.post(
                function_url, headers=headers, json=request, timeout=30
            )  # Increased timeout for network calls
            http_response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)

            try:
                response = http_response.json()
            except json.JSONDecodeError:
                error_message = f"Error: Received non-JSON response from URL. Status: {http_response.status_code}, Body: {http_response.text[:200]}..."  # Limit body size
        except requests.exceptions.Timeout:
            error_message = (
                f"Error: Request timed out after 30 seconds when calling {function_url}"
            )
        except requests.exceptions.RequestException as e:
            error_message = f"Error calling function URL {function_url}: {e}"
            if e.response is not None:
                error_message += f"\nStatus Code: {e.response.status_code}\nResponse: {e.response.text[:200]}..."  # Limit body size

    else:
        # Test locally
        if mock_context is None:
            # Create a simple mock context object
            class MockContext:
                def __init__(self):
                    self.function_name = "test_function"
                    self.function_version = "$LATEST"
                    self.memory_limit_in_mb = 128
                    self.aws_request_id = f"test-{int(time.time())}"

                def get_remaining_time_in_millis(self):
                    # Provide a reasonable remaining time, e.g., 10 seconds
                    return 10000

            mock_context = MockContext()

        # For local testing, ensure we're not using real cold start timing
        # This makes tests more consistent
        lambda_function._cold_start_time = (
            time.time() - lambda_function._initialization_time
        )

        try:
            # Make a deep copy of the request to avoid any reference issues
            request_copy = json.loads(json.dumps(request))

            # Call the lambda function directly with the copy
            response = lambda_function.lambda_handler(request_copy, mock_context)
        except Exception as e:
            error_message = f"Error calling local lambda_handler: {e}"
            logging.exception("Exception details:")  # Log traceback for local errors

    # Calculate and print the elapsed time
    elapsed_time = time.time() - start_time

    if error_message:
        print(f"\nError during test: {error_message}")
        print(f"Time elapsed before error: {elapsed_time:.2f} sec")
        return None  # Return None to indicate failure

    # Print summary timing information
    print_json(response, "Response")

    # Detailed timing summary
    # Initialization time is only relevant for local runs
    print(
        f"\n{request['header']['name']} Timing Summary ({'URL' if function_url else 'Local'}):"
    )
    if not function_url:
        print(f"  Initialization:  {lambda_function._initialization_time:.2f} sec")
        print(f"  Execution:       {elapsed_time:.2f} sec")
        total_time = lambda_function._initialization_time + elapsed_time
        print(
            f"  Total Time:      {total_time:.2f} sec {'<' if total_time <= TEST_TARGET_TIME else '>'} {TEST_TARGET_TIME:.2f} sec target"
        )
        # Show how much time margin remains
        margin = TEST_TARGET_TIME - total_time
        if margin >= 0:
            print(f"  Time Margin:     {margin:.2f} sec remaining")
        else:
            print(f"  Time Exceeded:   {abs(margin):.2f} sec over budget")
    else:
        # For URL tests, just show the request time
        print(f"  Request Time:    {elapsed_time:.2f} sec")
        # Comparison to target time might still be relevant depending on expected network latency
        print(
            f"  Comparison:      {elapsed_time:.2f} sec {'<' if elapsed_time <= TEST_TARGET_TIME else '>'} {TEST_TARGET_TIME:.2f} sec target (excluding network)"
        )

    return response


def clear_token_cache():
    """Remove the cached token file"""
    token_file = os.path.expanduser("~/.suno_test_token.json")
    if os.path.exists(token_file):
        try:
            os.remove(token_file)
            print("Token cache cleared.")
            return True
        except Exception as e:
            print(f"Error removing token cache: {str(e)}")
            return False
    else:
        print("No cached token found.")
        return True


def get_access_token(force_new=False):
    """Get the access token from a file or through OAuth"""
    token_file = os.path.expanduser("~/.suno_test_token.json")

    try:
        # If force_new is set, skip checking for existing token
        if not force_new and os.path.exists(token_file):
            with open(token_file, "r") as f:
                token_data = json.load(f)
                access_token = token_data.get("access_token")
                expires_at = token_data.get("expires_at")

                # Check if token is still valid
                if expires_at and datetime.fromisoformat(expires_at) > datetime.now():
                    print(f"Using existing access token (expires {expires_at})")
                    return access_token
                else:
                    print("Existing token expired.")
        else:
            if force_new:
                print("Forcing new authentication...")
            else:
                print("No valid token found. Starting OAuth flow...")

        # Get new token
        access_token = get_oauth_token()

        if access_token:
            # Save the token
            expires_at = (datetime.now() + timedelta(hours=1)).isoformat()
            with open(token_file, "w") as f:
                json.dump({"access_token": access_token, "expires_at": expires_at}, f)
            return access_token
        else:
            print("Failed to obtain access token.")
            return None
    except Exception as e:
        print(f"Error handling token: {e}")
        access_token = get_oauth_token()
        return access_token


def test_resource_urls(initiate_response, download_time=5):
    """Test URLs for audio, cover art, and lyrics in parallel after a successful Initiate.

    Args:
        initiate_response: The response from Initiate
        download_time: Number of seconds to download from audio stream

    Returns:
        dict: Results of the resource tests with status and file paths
    """
    if not initiate_response or "payload" not in initiate_response:
        console.print("No valid Initiate response to test resources", style="red")
        return None

    try:
        # Create a directory for output files if it doesn't exist
        output_dir = "resource_tests"
        os.makedirs(output_dir, exist_ok=True)

        # Extract the content ID and URLs from the response
        first_item = (
            initiate_response.get("payload", {})
            .get("playbackMethod", {})
            .get("firstItem", {})
        )
        content_id = first_item.get("id")

        if not content_id:
            console.print("No content ID found in Initiate response", style="red")
            return None

        console.print(
            f"\n[bold cyan]Testing resource URLs for content ID: {content_id}[/bold cyan]"
        )
        console.print(
            f"Audio stream will be downloaded for {download_time} seconds (use --download-time to change)"
        )

        # Extract the URLs and headers
        audio_url = first_item.get("stream", {}).get("uri")

        # Get the first (highest quality) cover art URL
        art_sources = first_item.get("metadata", {}).get("art", {}).get("sources", [])
        cover_art_url = art_sources[0].get("url") if art_sources else None

        lyrics_info = first_item.get("transcript", {})
        lyrics_url = lyrics_info.get("uri") if lyrics_info else None

        # Get any request headers needed for lyrics
        lyrics_headers = {}
        if lyrics_info and "headers" in lyrics_info:
            for header in lyrics_info["headers"]:
                lyrics_headers[header["name"]] = header["value"]

        # Define output file paths
        audio_file = os.path.join(output_dir, f"stream_{content_id}.mp3")
        cover_file = os.path.join(output_dir, f"image_{content_id}.jpeg")
        lyrics_file = os.path.join(output_dir, f"lyric_{content_id}.vtt")

        results = {
            "content_id": content_id,
            "audio": {"status": "not_tested", "file": audio_file},
            "cover": {"status": "not_tested", "file": cover_file},
            "lyrics": {"status": "not_tested", "file": lyrics_file},
        }

        # Define functions for each resource test
        def test_audio():
            if not audio_url:
                return {"status": "error", "message": "No audio URL found"}

            try:
                # Stream for the specified number of seconds and save to file
                console.print(
                    f"[cyan]Downloading audio stream for {download_time} seconds...[/cyan]"
                )
                start_time = time.time()
                with requests.get(
                    audio_url, stream=True, timeout=max(30, download_time + 5)
                ) as r:
                    r.raise_for_status()
                    console.print(
                        f"[cyan]Audio stream connected. Status: {r.status_code}[/cyan]"
                    )
                    with open(audio_file, "wb") as f:
                        # Download for specified time or until connection closes
                        total_bytes = 0
                        last_update = 0
                        for chunk in r.iter_content(chunk_size=8192):
                            elapsed = time.time() - start_time
                            if elapsed > download_time:
                                break
                            if chunk:
                                total_bytes += len(chunk)
                                f.write(chunk)
                                # Show progress every second
                                current_second = int(elapsed)
                                if current_second > last_update:
                                    console.print(
                                        f"[cyan]  Downloaded {total_bytes / 1024:.1f} KB in {elapsed:.1f} sec[/cyan]",
                                        end="\r",
                                    )
                                    last_update = current_second

                elapsed = time.time() - start_time
                file_size = os.path.getsize(audio_file)
                console.print()  # Add a newline after progress updates
                result = {
                    "status": "success" if file_size > 0 else "error",
                    "file": audio_file,
                    "size": file_size,
                    "message": f"Downloaded {file_size} bytes ({total_bytes / 1024:.1f} KB) in {elapsed:.1f} seconds",
                }
                # Print immediate result
                status_color = "green" if result["status"] == "success" else "red"
                console.print(
                    f"[bold]Audio[/bold]: [{status_color}]{result['status']}[/{status_color}] - {result['message']}"
                )
                return result
            except Exception as e:
                console.print(f"[bold red]Audio stream error: {str(e)}[/bold red]")

                # Generate and print equivalent curl command for debugging
                curl_command = f"curl -v '{audio_url}'"

                console.print("\n[yellow]Debug with this curl command:[/yellow]")
                console.print(curl_command)

                return {"status": "error", "message": str(e)}

        def test_cover_art():
            if not cover_art_url:
                return {"status": "error", "message": "No cover art URL found"}

            try:
                console.print("[cyan]Fetching cover art...[/cyan]")
                start_time = time.time()
                r = requests.get(cover_art_url, timeout=10)
                r.raise_for_status()
                elapsed = time.time() - start_time

                with open(cover_file, "wb") as f:
                    f.write(r.content)

                file_size = os.path.getsize(cover_file)
                result = {
                    "status": "success",
                    "file": cover_file,
                    "size": file_size,
                    "message": f"Downloaded {file_size} bytes in {elapsed:.2f} seconds",
                }
                # Print immediate result
                console.print(
                    f"[bold]Cover[/bold]: [green]success[/green] - {result['message']}"
                )
                return result
            except Exception as e:
                console.print(f"[bold red]Cover art error: {str(e)}[/bold red]")
                return {"status": "error", "message": str(e)}

        def test_lyrics():
            if not lyrics_url:
                return {"status": "error", "message": "No lyrics URL found"}

            try:
                console.print("[cyan]Fetching lyrics...[/cyan]")
                start_time = time.time()
                r = requests.get(lyrics_url, headers=lyrics_headers, timeout=10)
                r.raise_for_status()
                elapsed = time.time() - start_time

                content_type = r.headers.get("Content-Type", "unknown")

                with open(lyrics_file, "wb") as f:
                    f.write(r.content)

                file_size = os.path.getsize(lyrics_file)
                result = {
                    "status": "success",
                    "file": lyrics_file,
                    "size": file_size,
                    "message": f"Downloaded {file_size} bytes ({content_type}) in {elapsed:.2f} seconds",
                }
                # Print immediate result
                console.print(
                    f"[bold]Lyrics[/bold]: [green]success[/green] - {result['message']}"
                )
                return result
            except Exception as e:
                console.print(f"[bold red]Lyrics error: {str(e)}[/bold red]")

                # Generate and print equivalent curl command for debugging
                curl_command = "curl -v"

                # Add headers
                for header_name, header_value in lyrics_headers.items():
                    curl_command += f" -H '{header_name}: {header_value}'"

                # Add URL (in quotes to handle special characters)
                curl_command += f" '{lyrics_url}'"

                console.print("\n[yellow]Debug with this curl command:[/yellow]")
                console.print(curl_command)

                return {"status": "error", "message": str(e)}

        # Execute the tests in parallel
        console.print("[cyan]Testing all resources in parallel...[/cyan]")
        with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
            future_audio = executor.submit(test_audio) if audio_url else None
            future_cover = executor.submit(test_cover_art) if cover_art_url else None
            future_lyrics = executor.submit(test_lyrics) if lyrics_url else None

            if future_audio:
                results["audio"] = future_audio.result()
            if future_cover:
                results["cover"] = future_cover.result()
            if future_lyrics:
                results["lyrics"] = future_lyrics.result()

        # Print final summary
        console.print("\n[bold green]Resource Test Results Summary:[/bold green]")
        for resource_type, result in results.items():
            if resource_type == "content_id":
                continue

            status = result.get("status", "unknown")
            file_path = result.get("file", "N/A")

            if status == "success":
                console.print(f"  Saved to: {file_path}")

        return results

    except Exception as e:
        console.print(f"\n[bold red]Error testing resource URLs: {str(e)}[/bold red]")
        return None


def process_gpc_response(
    response,
    access_token,
    auto_initiate=0,
    print_raw_request=False,
    function_url=None,
    download_time=5,
):
    """Process a GetPlayableContent response and optionally auto-initiate playback

    Args:
        response: The response from GetPlayableContent
        access_token: The OAuth access token
        auto_initiate: Number of seconds to wait before auto-initiating (0 = disabled)
        print_raw_request: Whether to print the raw request JSON
        function_url: URL of the Lambda function to test against
        download_time: Number of seconds to download from audio stream
    """
    if response.get("header", {}).get("name") == "GetPlayableContent.Response":
        # Updated to handle content as a single object, not a list
        content = response.get("payload", {}).get("content", {})
        # Check if content has an ID
        content_id = content.get("id")
        if content_id:
            console.print(f"\nContent ID: {content_id}")

            # If auto-initiate is enabled (positive seconds value), automatically call Initiate after waiting
            if auto_initiate > 0:
                # Display countdown with Rich spinner and styling
                with Progress(
                    SpinnerColumn(),
                    TextColumn("[cyan]Waiting to initiate...[/cyan]"),
                    TimeRemainingColumn(),
                    transient=True,
                ) as progress:
                    task = progress.add_task(
                        "", total=auto_initiate, remaining=auto_initiate
                    )
                    for remaining in range(auto_initiate, 0, -1):
                        progress.update(task, advance=1, remaining=remaining)
                        time.sleep(1)

                initiate_request = create_initiate_request(access_token, content_id)

                # Reset cold start timer for this test
                lambda_function._cold_start_time = (
                    time.time() - lambda_function._initialization_time
                )

                # This is a separate Initiate execution
                initiate_response = test_lambda(
                    initiate_request,
                    print_raw_request=print_raw_request,
                    function_url=function_url,
                )

                # Test resource URLs if initiate was successful
                if (
                    initiate_response
                    and initiate_response.get("header", {}).get("name")
                    == "Initiate.Response"
                ):
                    test_resource_urls(initiate_response, download_time=download_time)

                return initiate_response
            else:
                # Otherwise ask for user confirmation
                test_initiate = (
                    input("Test Initiate with this content? (y/n): ").lower() == "y"
                )
                if test_initiate:
                    # This is a separate execution - starts timing here
                    initiate_request = create_initiate_request(access_token, content_id)

                    # Reset cold start timer for this test
                    lambda_function._cold_start_time = (
                        time.time() - lambda_function._initialization_time
                    )

                    initiate_response = test_lambda(
                        initiate_request,
                        print_raw_request=print_raw_request,
                        function_url=function_url,
                    )

                    # Test resource URLs if initiate was successful
                    if (
                        initiate_response
                        and initiate_response.get("header", {}).get("name")
                        == "Initiate.Response"
                    ):
                        test_resource_urls_option = (
                            input(
                                "Test resource URLs (audio, cover art, lyrics)? (y/n): "
                            ).lower()
                            == "y"
                        )
                        if test_resource_urls_option:
                            test_resource_urls(
                                initiate_response, download_time=download_time
                            )

                    return initiate_response

    return None


def create_gdc_request(access_token, genre="pop"):
    """Create a GetDisplayableContent request with optional genre filter"""
    return {
        "header": {
            "messageId": f"test-{int(time.time())}",
            "namespace": "Alexa.Media.Search",
            "name": "GetDisplayableContent",
            "payloadVersion": "3.0"
        },
        "payload": {
            "requestContext": {
                "user": {
                    "id": "7d2fa43ade80f308f0d761944f60f391",
                    "accessToken": access_token
                },
                "location": {
                    "originatingLocale": "en-US",
                    "countryCode": "US"
                },
                "advertising": {
                    "limitAdTracking": True,
                    "advertisingId": "00000000-0000-0000-0000-000000000000"
                },
                "apiAccessToken": None,
                "apiEndpoint": None
            },
            "filters": {
                "explicitLanguageAllowed": False
            },
            "policies": None,
            "endpoints": None,
            "rawText": None,
            "maxResultLimit": 8,
            "playQueuePreviewCriteria": None,
            "rankedSelectionCriteria": [{
                "type": "ATTRIBUTES",
                "id": "MULTI_ATTR_CRITERIA",
                "attributes": [{
                    "type": "GENRE",
                    "id": "YW1iaWVudA==",  # Base64 for "ambient", but we'll use rawValue
                    "rawValue": genre,
                    "resolvedEntities": []
                }],
                "completeness": {
                    "score": 1.0,
                    "bin": "HIGH"
                },
                "query": None
            }],
            "paginationContext": {
                "paginationContextType": None,
                "limits": {
                    "contentGroupsLimit": {
                        "maxLimit": 0,
                        "canPaginate": False
                    },
                    "contentItemsPerContentListLimit": {
                        "maxLimit": 8,
                        "canPaginate": False
                    }
                }
            }
        }
    }


def create_gdc_search_request(access_token, search_term="pop"):
    """Create a GetDisplayableContent request with natural language search query"""
    return {
        "header": {
            "messageId": f"test-{int(time.time())}",
            "namespace": "Alexa.Media.Search",
            "name": "GetDisplayableContent",
            "payloadVersion": "3.0"
        },
        "payload": {
            "requestContext": {
                "user": {
                    "id": "amzn1.ask.account.AMAXEMEU6ORSTK6P5ZBQP3FKC7AQUNSG7QLYEXBYAZTO2RQIRZ5UZGR7FWB2D7SR3WR6Q4SGATPADCQ7KGP5AFRD2VVUOZVGL2GKRG7ZYEEULWLEF7RMJOJWKGI57OD266FD3SCBG6KN2AXVX25YXEYTUEZBANSCKVB5LRYA7VHBIRTM3ZDLOO3NKQBZ6PSIIJ5KNQ5LPPYOZXLVINCIDNKW7DMRGRWIJHDLWIJ55ERA",
                    "accessToken": access_token
                },
                "location": {
                    "originatingLocale": "en-US",
                    "countryCode": "US"
                },
                "advertising": {
                    "limitAdTracking": True,
                    "advertisingId": "00000000-0000-0000-0000-000000000000"
                },
                "apiAccessToken": None,
                "apiEndpoint": None
            },
            "filters": {
                "explicitLanguageAllowed": False
            },
            "policies": None,
            "endpoints": None,
            "rawText": None,
            "maxResultLimit": 8,
            "playQueuePreviewCriteria": None,
            "rankedSelectionCriteria": [
                {
                    "type": "NL_QUERY",
                    "id": "NL_QUERY_CRITERIA",
                    "query": f"find the {search_term} music i created on suno"
                }
            ],
            "paginationContext": {
                "paginationContextType": None,
                "limits": {
                    "contentGroupsLimit": {
                        "maxLimit": 0,
                        "canPaginate": False
                    },
                    "contentItemsPerContentListLimit": {
                        "maxLimit": 8,
                        "canPaginate": False
                    }
                }
            }
        }
    }


def create_gdc_my_music_request(access_token):
    """Create a GetDisplayableContent request for browsing 'my music' created on Suno"""
    return {
        "header": {
            "messageId": f"test-{int(time.time())}",
            "namespace": "Alexa.Media.Search",
            "name": "GetDisplayableContent",
            "payloadVersion": "3.0"
        },
        "payload": {
            "requestContext": {
                "user": {
                    "id": "amzn1.ask.account.AMAXEMEU6ORSTK6P5ZBQP3FKC7AQUNSG7QLYEXBYAZTO2RQIRZ5UZGR7FWB2D7SR3WR6Q4SGATPADCQ7KGP5AFRD2VVUOZVGL2GKRG7ZYEEULWLEF7RMJOJWKGI57OD266FD3SCBG6KN2AXVX25YXEYTUEZBANSCKVB5LRYA7VHBIRTM3ZDLOO3NKQBZ6PSIIJ5KNQ5LPPYOZXLVINCIDNKW7DMRGRWIJHDLWIJ55ERA",
                    "accessToken": access_token
                },
                "location": {
                    "originatingLocale": "en-US",
                    "countryCode": "US"
                },
                "advertising": {
                    "limitAdTracking": True,
                    "advertisingId": "00000000-0000-0000-0000-000000000000"
                },
                "apiAccessToken": None,
                "apiEndpoint": None
            },
            "filters": {
                "explicitLanguageAllowed": False
            },
            "policies": None,
            "endpoints": None,
            "rawText": None,
            "maxResultLimit": 8,
            "playQueuePreviewCriteria": None,
            "rankedSelectionCriteria": [
                {
                    "type": "NL_QUERY",
                    "id": "NL_QUERY_CRITERIA",
                    "query": "find the music i created on suno"
                },
                {
                    "type": "ATTRIBUTES",
                    "id": "MULTI_ATTR_CRITERIA",
                    "attributes": [
                        {
                            "type": "MEDIA_TYPE",
                            "id": "bXVzaWM=",
                            "rawValue": "music",
                            "value": "TRACK"
                        }
                    ],
                    "completeness": {
                        "score": 1,
                        "bin": "HIGH"
                    },
                    "query": None
                }
            ],
            "paginationContext": {
                "paginationContextType": None,
                "limits": {
                    "contentGroupsLimit": {
                        "maxLimit": 0,
                        "canPaginate": False
                    },
                    "contentItemsPerContentListLimit": {
                        "maxLimit": 8,
                        "canPaginate": False
                    }
                }
            }
        }
    }


def test_compound_song_flow(access_token, query="Create a song about a cat", print_raw_request=False, function_url=None, download_time=5):
    """Run a compound test that creates a song, initiates playback, and tests navigation"""
    console.print("\n[bold cyan]Starting compound song creation and navigation test[/bold cyan]")
    console.print(f"Creating song with prompt: {query}")
    
    # Step 1: Create song with GetPlayableContent
    gpc_request = create_gpc_request(access_token, query)
    lambda_function._cold_start_time = time.time() - lambda_function._initialization_time
    
    gpc_response = test_lambda(
        gpc_request, print_raw_request=print_raw_request, function_url=function_url
    )
    
    if not gpc_response or gpc_response.get("header", {}).get("name") != "GetPlayableContent.Response":
        console.print("[bold red]Failed at step 1: GetPlayableContent failed[/bold red]")
        return False
    
    # Extract content ID from GPC response
    content = gpc_response.get("payload", {}).get("content", {})
    content_id = content.get("id")
    
    if not content_id:
        console.print("[bold red]Failed at step 1: No content ID in GetPlayableContent response[/bold red]")
        return False
    
    console.print(f"[green]✓ Step 1: Song created with content ID: {content_id}[/green]")
    
    # Step 2: Initiate playback
    console.print("\n[cyan]Step 2: Initiating playback...[/cyan]")
    # Wait 5 seconds for generation to complete
    with Progress(
        SpinnerColumn(),
        TextColumn("[cyan]Waiting for generation to complete...[/cyan]"),
        TimeRemainingColumn(),
        transient=True,
    ) as progress:
        task = progress.add_task("", total=5, remaining=5)
        for remaining in range(5, 0, -1):
            progress.update(task, advance=1, remaining=remaining)
            time.sleep(1)
    
    initiate_request = create_initiate_request(access_token, content_id)
    lambda_function._cold_start_time = time.time() - lambda_function._initialization_time
    
    initiate_response = test_lambda(
        initiate_request, print_raw_request=print_raw_request, function_url=function_url
    )
    
    if not initiate_response or initiate_response.get("header", {}).get("name") != "Initiate.Response":
        console.print("[bold red]Failed at step 2: Initiate failed[/bold red]")
        return False
    
    # Extract clip ID and queue ID from Initiate response
    playback_method = initiate_response.get("payload", {}).get("playbackMethod", {})
    first_item = playback_method.get("firstItem", {})
    clip_id = first_item.get("id")
    queue_id = playback_method.get("id")
    
    if not clip_id or not queue_id:
        console.print("[bold red]Failed at step 2: No clip ID or queue ID in Initiate response[/bold red]")
        return False
    
    console.print(f"[green]✓ Step 2: Playback initiated with clip ID: {clip_id}[/green]")
    
    # Optionally test resource URLs
    test_resource_urls(initiate_response, download_time=download_time)
    
    # Step 3: Test GetNextItem
    console.print("\n[cyan]Step 3: Testing GetNextItem to verify second version...[/cyan]")
    get_next_item_request = create_get_next_item_request(access_token, clip_id, queue_id)
    lambda_function._cold_start_time = time.time() - lambda_function._initialization_time
    
    next_item_response = test_lambda(
        get_next_item_request, print_raw_request=print_raw_request, function_url=function_url
    )
    
    if not next_item_response or next_item_response.get("header", {}).get("name") != "GetNextItem.Response":
        console.print("[bold red]Failed at step 3: GetNextItem failed[/bold red]")
        return False
    
    next_item = next_item_response.get("payload", {}).get("item", {})
    next_item_id = next_item.get("id")
    
    if not next_item_id:
        console.print("[bold red]Failed at step 3: No item ID in GetNextItem response[/bold red]")
        return False
    
    # Extract metadata to verify it's actually a second version of the same song
    # Get original song metadata
    original_metadata = first_item.get("metadata", {})
    # Title is in metadata.name.display
    original_title = original_metadata.get("name", {}).get("display", "Unknown")
    # Artist might be in a different location depending on the response structure
    original_artist = original_metadata.get("artist", "Unknown")
    
    # Get next item metadata
    next_metadata = next_item.get("metadata", {})
    next_title = next_metadata.get("name", {}).get("display", "Unknown")
    next_artist = next_metadata.get("artist", "Unknown")
    
    # Check if items are different (different versions)
    if next_item_id == clip_id:
        console.print("[bold yellow]Warning: GetNextItem returned the same clip ID[/bold yellow]")
    else:
        console.print(f"[green]✓ Step 3: GetNextItem returned different clip ID: {next_item_id}[/green]")
    
    # Compare metadata to check if it's a second version of the same song
    console.print("\n[cyan]Comparing metadata between original and next item:[/cyan]")
    console.print(f"Original: Title='{original_title}', Artist='{original_artist}'")
    console.print(f"Next Item: Title='{next_title}', Artist='{next_artist}'")
    
    if original_title == next_title:
        console.print("[green]✓ Titles match - confirmed to be second version of same song[/green]")
    else:
        console.print("[bold yellow]Warning: Titles don't match - next item may not be second version of the same song[/bold yellow]")
        console.print("[yellow]This may indicate an issue with song pairs in the backend[/yellow]")
    
    # Step 4: Test GetPreviousItem
    console.print("\n[cyan]Step 4: Testing GetPreviousItem to verify navigation to older song...[/cyan]")
    get_previous_item_request = create_get_previous_item_request(access_token, clip_id, queue_id)
    lambda_function._cold_start_time = time.time() - lambda_function._initialization_time
    
    previous_item_response = test_lambda(
        get_previous_item_request, print_raw_request=print_raw_request, function_url=function_url
    )
    
    if not previous_item_response or previous_item_response.get("header", {}).get("name") != "GetPreviousItem.Response":
        console.print("[bold red]Failed at step 4: GetPreviousItem failed[/bold red]")
        return False
    
    # Check if there is a previous item
    previous_item = previous_item_response.get("payload", {}).get("item")
    
    # Handle the case where there's no previous item (which is a valid response)
    if previous_item is None:
        is_queue_finished = previous_item_response.get("payload", {}).get("isQueueFinished", False)
        if is_queue_finished:
            console.print("[yellow]No previous item available (isQueueFinished: True)[/yellow]")
        else:
            console.print("[yellow]No previous item available, but queue is not marked as finished[/yellow]")
        console.print("[green]✓ Step 4: GetPreviousItem returned a valid response with no previous item[/green]")
    else:
        # There is a previous item, check its ID
        previous_item_id = previous_item.get("id")
        
        if not previous_item_id:
            console.print("[bold red]Failed at step 4: No item ID in GetPreviousItem response[/bold red]")
            return False
        
        if previous_item_id == clip_id:
            console.print("[bold yellow]Warning: GetPreviousItem returned the same clip ID[/bold yellow]")
        else:
            console.print(f"[green]✓ Step 4: GetPreviousItem returned different clip ID: {previous_item_id}[/green]")
    
    console.print("\n[bold green]Compound test completed successfully![/bold green]")
    return True


def test_repeat_flow(access_token, print_raw_request=False, function_url=None):
    """
    Run a compound test for Initiate -> SetLoop -> GetNextItem (repeat ON) -> SetLoop -> GetNextItem (repeat OFF).
    """
    console.print("\n[bold cyan]Starting compound test for Repeat functionality[/bold cyan]")
    
    # Step 1: Create a song to get a content_id
    console.print("\n[cyan]Step 1: Creating a song to get a content ID...[/cyan]")
    gpc_request = create_gpc_request(access_token, "A song to test repeat mode")
    gpc_response = test_lambda(
        gpc_request, print_raw_request=print_raw_request, function_url=function_url
    )
    if not gpc_response or gpc_response.get("header", {}).get("name") != "GetPlayableContent.Response":
        console.print("[bold red]Failed at step 1: GetPlayableContent failed[/bold red]")
        return False
    
    content_id = gpc_response.get("payload", {}).get("content", {}).get("id")
    if not content_id:
        console.print("[bold red]Failed at step 1: No content ID in GPC response[/bold red]")
        return False
    console.print(f"[green]✓ Step 1: Song created with content ID: {content_id}[/green]")
    
    # Wait for generation
    console.print("[cyan]Waiting 5 seconds for song generation to settle...[/cyan]")
    time.sleep(5)
    
    # Step 2: Initiate playback, which sets initial repeat status to OFF
    console.print("\n[cyan]Step 2: Initiating playback (initial repeat=OFF)...[/cyan]")
    initiate_request = create_initiate_request(access_token, content_id)
    initiate_response = test_lambda(
        initiate_request, print_raw_request=print_raw_request, function_url=function_url
    )
    if not initiate_response or initiate_response.get("header", {}).get("name") != "Initiate.Response":
        console.print("[bold red]Failed at step 2: Initiate failed[/bold red]")
        return False
        
    playback_method = initiate_response.get("payload", {}).get("playbackMethod", {})
    first_item = playback_method.get("firstItem", {})
    original_clip_id = first_item.get("id")
    queue_id = playback_method.get("id")

    if not original_clip_id or not queue_id:
        console.print("[bold red]Failed at step 2: No clip ID or queue ID in Initiate response[/bold red]")
        return False
    console.print(f"[green]✓ Step 2: Playback initiated for clip ID: {original_clip_id} in queue: {queue_id}[/green]")
    
    # Step 3: Send SetLoop to turn ON repeat mode
    console.print("\n[cyan]Step 3: Setting loop mode to ON (via SetLoop)...[/cyan]")
    set_loop_on_request = create_set_loop_request(access_token, original_clip_id, queue_id, enable=True)
    set_loop_on_response = test_lambda(
        set_loop_on_request, print_raw_request=print_raw_request, function_url=function_url
    )
    # A simple success response is enough
    if not set_loop_on_response or "header" not in set_loop_on_response:
        console.print("[bold red]Failed at step 3: SetLoop(enable=True) request failed[/bold red]")
        return False
    console.print("[green]✓ Step 3: SetLoop(enable=True) sent successfully.[/green]")

    # Step 4: System-initiated GetNextItem with REPEAT=ON
    console.print("\n[cyan]Step 4: Testing system-initiated GetNextItem (expecting same song)...[/cyan]")
    gni_repeat_on_request = create_get_next_item_request(
        access_token, original_clip_id, queue_id, is_user_initiated=False
    )
    gni_repeat_on_response = test_lambda(
        gni_repeat_on_request, print_raw_request=print_raw_request, function_url=function_url
    )
    
    repeated_item = gni_repeat_on_response.get("payload", {}).get("item", {})
    repeated_clip_id = repeated_item.get("id")
    
    if repeated_clip_id == original_clip_id:
        console.print(f"[green]✓ Step 4: Success! Received same clip ID: {repeated_clip_id}[/green]")
    else:
        console.print(f"[bold red]Failed at step 4: Expected clip ID {original_clip_id} but got {repeated_clip_id}[/bold red]")
        return False

    # Step 5: Send SetLoop to turn OFF repeat mode
    console.print("\n[cyan]Step 5: Setting loop mode to OFF (via SetLoop)...[/cyan]")
    set_loop_off_request = create_set_loop_request(access_token, original_clip_id, queue_id, enable=False)
    set_loop_off_response = test_lambda(
        set_loop_off_request, print_raw_request=print_raw_request, function_url=function_url
    )
    if not set_loop_off_response or "header" not in set_loop_off_response:
        console.print("[bold red]Failed at step 5: SetLoop(enable=False) request failed[/bold red]")
        return False
    console.print("[green]✓ Step 5: SetLoop(enable=False) sent successfully.[/green]")

    # Step 6: System-initiated GetNextItem with REPEAT=OFF
    console.print("\n[cyan]Step 6: Testing system-initiated GetNextItem (expecting next song)...[/cyan]")
    gni_repeat_off_request = create_get_next_item_request(
        access_token, original_clip_id, queue_id, is_user_initiated=False
    )
    gni_repeat_off_response = test_lambda(
        gni_repeat_off_request, print_raw_request=print_raw_request, function_url=function_url
    )
    
    next_item = gni_repeat_off_response.get("payload", {}).get("item", {})
    next_clip_id = next_item.get("id")
    
    if next_clip_id and next_clip_id != original_clip_id:
        console.print(f"[green]✓ Step 6: Success! Received a different clip ID: {next_clip_id}[/green]")
    elif not next_clip_id:
        # This is also a valid outcome if there is no next song in the queue
        is_finished = gni_repeat_off_response.get("payload", {}).get("isQueueFinished", False)
        if is_finished:
             console.print(f"[green]✓ Step 6: Success! No next item returned and queue is finished.[/green]")
        else:
            console.print("[bold red]Failed at step 6: GetNextItem did not return an item, but queue not finished.[/bold red]")
            return False
    else:
        console.print(f"[bold red]Failed at step 6: Expected a different clip ID but got the same one: {next_clip_id}[/bold red]")
        return False
        
    console.print("\n[bold green]Repeat functionality compound test completed successfully![/bold green]")
    return True


def interactive_mode(
    access_token,
    auto_initiate=0,
    print_raw_request=False,
    function_url=None,
    download_time=5,
):
    """Run in interactive mode, allowing the user to select what to test"""
    # Ask the user what to test
    print("\nWhat would you like to test?")
    print("1. Generate a song with a custom prompt")
    print("2. Play my most recent song")
    print("3. Initiate playback with a specific song ID")
    print("4. Get next song in queue")
    print("5. Get previous song in queue")
    print("6. Browse my library (GetDisplayableContent)")
    print("7. Search my library (GetDisplayableContent)")
    print("8. Full flow test (Create -> Initiate -> Nav)")
    print("9. Repeat mode test (Initiate -> GetNextItem)")
    choice = input("Enter choice (1-9): ")

    if choice == "1":
        # Test GetPlayableContent with query
        query = (
            input("Enter song prompt (or press Enter for default): ")
            or "Create a song about a cat"
        )
        gpc_request = create_gpc_request(access_token, query)
        response = test_lambda(
            gpc_request, print_raw_request=print_raw_request, function_url=function_url
        )
        process_gpc_response(
            response, access_token, auto_initiate, print_raw_request, function_url, download_time
        )
        
    elif choice == "2":
        # Test "Play my music"
        request = create_gpc_play_my_music_request(access_token)
        response = test_lambda(
            request, print_raw_request=print_raw_request, function_url=function_url
        )
        process_gpc_response(
            response, access_token, auto_initiate, print_raw_request, function_url, download_time
        )

    elif choice == "3":
        # Test with initiate_specific_content.json
        content_id = input("Enter the Content ID to initiate: ")
        if content_id:
            request = create_initiate_request(access_token, content_id)
            initiate_response = test_lambda(
                request, print_raw_request=print_raw_request, function_url=function_url
            )
            test_resource_urls(initiate_response, download_time=download_time)
        else:
            print("Content ID cannot be empty.")

    elif choice == "4":
        # Test GetNextItem
        clip_id = input("Enter the current clip ID: ")
        queue_id = input("Enter the current queue ID: ")
        if clip_id and queue_id:
            request = create_get_next_item_request(access_token, clip_id, queue_id)
            response = test_lambda(
                request, print_raw_request=print_raw_request, function_url=function_url
            )
            process_gpc_response(
                response, access_token, auto_initiate, print_raw_request, function_url, download_time
            )
        else:
            print("Clip ID and Queue ID cannot be empty.")

    elif choice == "5":
        # Test GetPreviousItem
        clip_id = input("Enter the current clip ID: ")
        queue_id = input("Enter the current queue ID: ")
        if clip_id and queue_id:
            request = create_get_previous_item_request(access_token, clip_id, queue_id)
            response = test_lambda(
                request, print_raw_request=print_raw_request, function_url=function_url
            )
            process_gpc_response(
                response, access_token, auto_initiate, print_raw_request, function_url, download_time
            )
        else:
            print("Clip ID and Queue ID cannot be empty.")

    elif choice == "6":
        # Test GetDisplayableContent with optional genre
        use_genre = input("Filter by genre? (y/n): ").lower() == "y"
        
        if use_genre:
            genre = input("Enter genre (or press Enter for default 'pop'): ") or "pop"
            print(f"Using genre filter: {genre}")
            gdc_request = create_gdc_request(access_token, genre)
        else:
            # Create an empty GDC request (no criteria)
            gdc_request = create_gdc_request(access_token)
            gdc_request["payload"]["rankedSelectionCriteria"] = []
            print("Using empty browse (no genre filter)")
        
        response = test_lambda(
            gdc_request, print_raw_request=print_raw_request, function_url=function_url
        )
        print_json(response, "GetDisplayableContent response")

    elif choice == "7":
        # Test GetDisplayableContent search
        search_term = input("Enter search term (or press Enter for default 'pop'): ") or "pop"
        print(f"Searching for '{search_term}'")
        gdc_request = create_gdc_search_request(access_token, search_term)
        response = test_lambda(
            gdc_request, print_raw_request=print_raw_request, function_url=function_url
        )
        print_json(response, "GetDisplayableContent Search response")
        
    elif choice == "8":
        # Compound test: Create song -> Initiate -> GetNextItem -> GetPreviousItem
        query = (
            input("Enter song prompt (or press Enter for default): ")
            or "Create a song about a cat"
        )
        test_compound_song_flow(
            access_token, 
            query=query, 
            print_raw_request=print_raw_request, 
            function_url=function_url, 
            download_time=download_time
        )

    elif choice == "9":
        # Repeat mode test
        test_repeat_flow(
            access_token,
            print_raw_request=print_raw_request,
            function_url=function_url
        )
        
    else:
        print("Invalid choice.")


def test_generate_endpoint(access_token):
    """Test the generate endpoint directly to diagnose issues"""
    print("\nTesting generate endpoint directly...")

    try:
        # Prepare the request data
        data = {
            "topic": "Test song - diagnosing API issue",  # The API expects 'topic' as the main field
            "model": "chirp-v4-h-api",
            "extra": {"add_to_playlist": True},
        }

        # Set up headers with the access token
        headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
            "x-auth-type": "oauth",  # Required by Suno API
        }

        print_json(data, "Request data")
        print(f"Authorization: Bearer {access_token[:10]}...{access_token[-5:]}")

        # Make the request to Suno API
        response = requests.post(
            "https://studio-api.prod.suno.com/api/v2/external/oauth/generate",
            headers=headers,
            json=data,
            timeout=10,
        )

        # Print complete response details
        print(f"Status code: {response.status_code}")
        print(f"Response headers: {dict(response.headers)}")

        try:
            json_response = response.json()
            print_json(json_response, "Response JSON")
        except json.JSONDecodeError:
            print("Response is not valid JSON:")
            print(response.text)

        return response.status_code == 200

    except Exception as e:
        print(f"Error during generate test: {str(e)}")
        if hasattr(e, "response") and e.response:
            print(f"Error response: {e.response.text}")
        return False


def main():
    parser = argparse.ArgumentParser(
        description="Test the Lambda function with requests"
    )
    parser.add_argument(
        "--auto-initiate",
        "-a",
        type=int,
        nargs="?",  # Make it optional
        const=5,  # Default value when flag is specified without a value
        default=5,  # Default when flag is not specified at all (changed from 0 to 5)
        metavar="SECONDS",
        help="Automatically call Initiate after successful GetPlayableContent with SECONDS delay (default: 5 seconds)",
    )
    parser.add_argument("--output", "-o", help="Save response to a file")
    parser.add_argument(
        "--raw-request",
        "-r",
        action="store_true",
        help="Print the raw request JSON for AWS Lambda console testing",
    )
    parser.add_argument(
        "--clear-token",
        "-c",
        action="store_true",
        help="Clear the token cache file to force re-authentication",
    )
    parser.add_argument(
        "--log-level",
        "-l",
        choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
        default="INFO",
        help="Set the logging level (default: INFO)",
    )
    parser.add_argument(
        "--timing-breakdown",
        "-b",
        action="store_true",
        help="Show detailed timing breakdown",
    )
    parser.add_argument(
        "--url", help="URL of the deployed Lambda function to test against"
    )
    parser.add_argument(
        "--clip-id", help="Test GetPlayableContent with a specific clip ID"
    )
    parser.add_argument(
        "--test-resources",
        "-t",
        action="store_true",
        help="Automatically test resource URLs after successful Initiate",
    )
    parser.add_argument(
        "--test-response-file",
        help="Test resource URLs from a saved Initiate response file",
    )
    parser.add_argument(
        "--download-time",
        type=int,
        default=5,
        help="Number of seconds to download from audio stream (default: 5)",
    )
    parser.add_argument(
        "--prompt",
        type=str,
        help="Generate a song with the specified prompt",
    )
    args = parser.parse_args()

    # Set log level based on argument
    log_level = getattr(logging, args.log_level)
    logging.getLogger().setLevel(log_level)
    lambda_function.logger.setLevel(log_level)

    # Special case: If testing resources from a file, do that first and exit
    if args.test_response_file:
        try:
            with open(args.test_response_file, "r") as f:
                response_data = json.load(f)

            print(f"Testing resources from file: {args.test_response_file}")
            test_resource_urls(response_data, args.download_time)
            return 0
        except Exception as e:
            print(f"Error testing resources from file: {e}")
            return 1

    # Clear token cache if requested
    if args.clear_token:
        clear_token_cache()

    # Get access token - this happens before timing starts
    access_token = get_access_token(force_new=args.clear_token)

    # If no valid token, offer to clear and retry
    if not access_token:
        print("\nFailed to obtain a valid access token.")
        retry = (
            input(
                "Would you like to clear the token cache and try again? (y/n): "
            ).lower()
            == "y"
        )
        if retry:
            clear_token_cache()
            access_token = get_access_token(force_new=True)
            if not access_token:
                print("Still unable to obtain a valid token. Exiting.")
                return 1
        else:
            print("Exiting.")
            return 1

    # Test with specific clip ID if provided
    if args.clip_id:
        print(f"\nTesting GetPlayableContent with clip ID: {args.clip_id}")
        # Get the template request
        gpc_request = create_simple_gpc_request(access_token)

        # Replace the placeholder ID with the real clip ID
        criteria = gpc_request["payload"]["rankedSelectionCriteria"][0]
        entity = criteria["attributes"][0]["resolvedEntities"][0]

        # Update the entity ID
        original_id = entity["entityId"]
        entity["entityId"] = args.clip_id

        print(f"Updated entity ID from {original_id} to {args.clip_id}")

        # Reset initialization time to simulate a fresh Lambda environment
        lambda_function._cold_start_time = (
            time.time() - lambda_function._initialization_time
        )

        response = test_lambda(
            gpc_request, print_raw_request=args.raw_request, function_url=args.url
        )

        # Process the response - this may include initiate request
        process_gpc_response(
            response,
            access_token,
            args.auto_initiate,
            args.raw_request,
            args.url,
            args.download_time,
        )

        return 0
    
    # Test with specific prompt if provided
    elif args.prompt:
        print(f"\nGenerating song with prompt: {args.prompt}")
        
        # Create GPC request with the provided prompt
        gpc_request = create_gpc_request(access_token, args.prompt)
        
        # Reset initialization time
        lambda_function._cold_start_time = (
            time.time() - lambda_function._initialization_time
        )
        
        # Execute the request
        response = test_lambda(
            gpc_request, print_raw_request=args.raw_request, function_url=args.url
        )
        
        # Process the response
        process_gpc_response(
            response,
            access_token,
            args.auto_initiate, 
            args.raw_request,
            args.url,
            args.download_time,
        )
        
        return 0

    else:
        # Run in interactive mode
        interactive_mode(
            access_token,
            args.auto_initiate,
            args.raw_request,
            args.url,
            args.download_time,
        )

    return 0


if __name__ == "__main__":
    sys.exit(main())
