"""
Test Script for User Clustering Pipeline
=======================================
Creates synthetic test data and validates the clustering pipeline end-to-end.
"""

import pandas as pd
import numpy as np
import json
import os
import shutil
from datetime import datetime, timedelta
from pathlib import Path
import subprocess
import sys


def create_test_data():
    """Create synthetic test data representing different user types."""
    print("Creating synthetic test data...")

    # Set random seed for reproducibility
    np.random.seed(42)

    # Initialize lists to store data
    total_clips = []
    reactions = []
    boosts = []
    playlists = []

    # Current timestamp
    base_time = datetime(2024, 1, 1)

    # User ID counter
    user_id_counter = 1000

    # 1. Create BOT users (20 users)
    print("Creating bot users...")
    for i in range(20):
        user_id = f"bot_{user_id_counter + i}"

        # Bots create many clips rapidly
        creation_time = base_time
        for j in range(100):  # Each bot creates 100 clips
            clip_id = f"clip_{user_id}_{j}"

            # Rapid creation (every 30 seconds)
            creation_time += timedelta(seconds=30)

            clip = {
                "id": clip_id,
                "user_id": user_id,
                "created_at": creation_time,
                "updated_at": creation_time,
                "metadata": json.dumps({}),  # No lyrics generation
                "model_name": "model_v1",  # Always same model
                "task": "generate",
                "source": "web",
                "creation_source": "api",
                "duration": 30,
                "is_public": True,
                "is_deleted": False,
                "is_pro_user": False,
                "play_count": np.random.randint(0, 10),
                "upvote_count": 0,
                "prompt_text": f"prompt {j % 5}",  # Reuses prompts
                "continued_parent": None,
            }
            total_clips.append(clip)

            # Minimal boosts data
            boost = {
                "clip_id": clip_id,
                "created_at": creation_time,
                "download_audio_count": 0,
                "download_video_count": 0,
                "share_count": 0,
                "reuse_prompt_count": j % 5,  # High reuse
            }
            boosts.append(boost)

        # Bots don't react to anything (no consumption)

    user_id_counter += 20

    # 2. Create CASUAL users (30 users)
    print("Creating casual users...")
    for i in range(30):
        user_id = f"casual_{user_id_counter + i}"

        # Casual users create moderate number of clips
        creation_time = base_time
        num_clips = np.random.randint(10, 30)

        for j in range(num_clips):
            clip_id = f"clip_{user_id}_{j}"

            # Random gaps between creations (hours to days)
            creation_time += timedelta(hours=np.random.randint(1, 48))

            # 70% of clips have lyrics generation
            metadata = {}
            if np.random.random() < 0.7:
                metadata["gpt_description_prompt"] = (
                    f"Write lyrics about {np.random.choice(['love', 'life', 'happiness'])}"
                )

            clip = {
                "id": clip_id,
                "user_id": user_id,
                "created_at": creation_time,
                "updated_at": creation_time,
                "metadata": json.dumps(metadata),
                "model_name": np.random.choice(["model_v1", "model_v2"]),
                "task": np.random.choice(["generate", "extend"]),
                "source": np.random.choice(["web", "ios", "android"]),
                "creation_source": np.random.choice(["web", "mobile"]),
                "duration": np.random.randint(20, 40),
                "is_public": np.random.random() < 0.8,
                "is_deleted": False,
                "is_pro_user": False,  # Not pro
                "play_count": np.random.randint(10, 100),
                "upvote_count": np.random.randint(0, 5),
                "prompt_text": f"casual prompt {j}",
                "continued_parent": None,
            }
            total_clips.append(clip)

            # Moderate engagement
            boost = {
                "clip_id": clip_id,
                "created_at": creation_time,
                "download_audio_count": np.random.randint(0, 5),
                "download_video_count": np.random.randint(0, 2),
                "share_count": np.random.randint(0, 3),
                "reuse_prompt_count": 0,
            }
            boosts.append(boost)

            # Add to playlist sometimes
            if np.random.random() < 0.3:
                playlist = {
                    "id": f"pl_{len(playlists)}",
                    "clip_id": clip_id,
                    "playlist_id": f"playlist_{i % 5}",
                    "relative_index": j,
                    "updated_at": creation_time,
                }
                playlists.append(playlist)

        # Casual users have some reactions
        for _ in range(np.random.randint(5, 20)):
            reaction = {
                "id": f"reaction_{len(reactions)}",
                "user_id": user_id,
                "clip_id": np.random.choice([c["id"] for c in total_clips]),
                "reaction_type": "play",
                "play_count": 1,
                "updated_at": creation_time,
            }
            reactions.append(reaction)

    user_id_counter += 30

    # 3. Create PRO SERIOUS users (20 users)
    print("Creating pro serious users...")
    for i in range(20):
        user_id = f"pro_{user_id_counter + i}"

        # Pro users create quality content regularly
        creation_time = base_time
        num_clips = np.random.randint(40, 80)

        for j in range(num_clips):
            clip_id = f"clip_{user_id}_{j}"

            # Regular creation pattern
            creation_time += timedelta(hours=np.random.randint(12, 36))

            # Rarely use lyrics generation (they write their own)
            metadata = {}
            if np.random.random() < 0.1:
                metadata["gpt_description_prompt"] = "Professional prompt"

            clip = {
                "id": clip_id,
                "user_id": user_id,
                "created_at": creation_time,
                "updated_at": creation_time,
                "metadata": json.dumps(metadata),
                "model_name": np.random.choice(
                    ["model_v2", "model_v3", "model_v4"]
                ),  # Uses latest models
                "task": np.random.choice(
                    ["generate", "extend", "remix", "cover"]
                ),  # Diverse tasks
                "source": np.random.choice(["web", "ios"]),  # Professional platforms
                "creation_source": np.random.choice(["studio", "web", "pro_tools"]),
                "duration": np.random.randint(30, 60),  # Longer clips
                "is_public": True,
                "is_deleted": np.random.random() < 0.05,  # Low deletion rate
                "is_pro_user": True,  # Pro user
                "play_count": np.random.randint(100, 1000),  # High engagement
                "upvote_count": np.random.randint(10, 50),
                "prompt_text": f"professional prompt version {j}",
                "continued_parent": f"clip_{user_id}_{j-1}"
                if j > 0 and np.random.random() < 0.3
                else None,
            }
            total_clips.append(clip)

            # High engagement
            boost = {
                "clip_id": clip_id,
                "created_at": creation_time,
                "download_audio_count": np.random.randint(5, 20),
                "download_video_count": np.random.randint(2, 10),
                "share_count": np.random.randint(5, 15),
                "reuse_prompt_count": np.random.randint(0, 5),
            }
            boosts.append(boost)

            # Often in playlists
            if np.random.random() < 0.6:
                playlist = {
                    "id": f"pl_{len(playlists)}",
                    "clip_id": clip_id,
                    "playlist_id": f"playlist_pro_{i}",
                    "relative_index": j % 10,
                    "updated_at": creation_time,
                }
                playlists.append(playlist)

        # Pro users are active consumers too
        for _ in range(np.random.randint(50, 100)):
            reaction = {
                "id": f"reaction_{len(reactions)}",
                "user_id": user_id,
                "clip_id": np.random.choice([c["id"] for c in total_clips]),
                "reaction_type": "play",
                "play_count": 1,
                "updated_at": creation_time,
            }
            reactions.append(reaction)

    user_id_counter += 20

    # 4. Create OTHER/NORMAL users (30 users)
    print("Creating other/normal users...")
    for i in range(30):
        user_id = f"normal_{user_id_counter + i}"

        # Variable behavior
        creation_time = base_time
        num_clips = np.random.randint(5, 25)

        for j in range(num_clips):
            clip_id = f"clip_{user_id}_{j}"

            # Irregular creation pattern
            creation_time += timedelta(days=np.random.randint(1, 7))

            # Sometimes use lyrics
            metadata = {}
            if np.random.random() < 0.3:
                metadata["gpt_description_prompt"] = "Some lyrics"

            clip = {
                "id": clip_id,
                "user_id": user_id,
                "created_at": creation_time,
                "updated_at": creation_time,
                "metadata": json.dumps(metadata),
                "model_name": np.random.choice(["model_v1", "model_v2"]),
                "task": "generate",
                "source": np.random.choice(["web", "ios", "android"]),
                "creation_source": "web",
                "duration": 30,
                "is_public": np.random.random() < 0.6,
                "is_deleted": np.random.random() < 0.1,
                "is_pro_user": np.random.random() < 0.2,  # Some are pro
                "play_count": np.random.randint(5, 50),
                "upvote_count": np.random.randint(0, 10),
                "prompt_text": f"normal prompt {j}",
                "continued_parent": None,
            }
            total_clips.append(clip)

            # Variable engagement
            boost = {
                "clip_id": clip_id,
                "created_at": creation_time,
                "download_audio_count": np.random.randint(0, 10),
                "download_video_count": np.random.randint(0, 5),
                "share_count": np.random.randint(0, 5),
                "reuse_prompt_count": 0,
            }
            boosts.append(boost)

        # Some reactions
        for _ in range(np.random.randint(10, 30)):
            reaction = {
                "id": f"reaction_{len(reactions)}",
                "user_id": user_id,
                "clip_id": np.random.choice([c["id"] for c in total_clips]),
                "reaction_type": "play",
                "play_count": 1,
                "updated_at": creation_time,
            }
            reactions.append(reaction)

    # Create DataFrames
    total_clip_df = pd.DataFrame(total_clips)
    reaction_df = pd.DataFrame(reactions)
    boosts_action_df = pd.DataFrame(boosts)
    playlist_clip_df = pd.DataFrame(playlists)

    # Add missing columns with default values
    reaction_df["skip_count"] = 0
    reaction_df["flagged"] = False
    reaction_df["is_pro_user"] = reaction_df["user_id"].str.contains("pro_")

    boosts_action_df["updated_at"] = boosts_action_df["created_at"]
    boosts_action_df["first_published_at"] = boosts_action_df["created_at"]
    boosts_action_df["is_public_approved"] = True

    # Add remaining columns with defaults
    for col in [
        "time_used",
        "status",
        "discord_message_id",
        "prompt_id",
        "request_id",
        "is_generated",
        "s3_id",
        "batch_index",
        "daily_theme_id",
        "image_s3_id",
        "dislike_count",
        "flag_count",
        "skip_count",
        "title",
        "slug",
        "allow_comments",
        "is_hidden",
        "display_tags",
        "clip_type",
        "edited_clip_id",
        "date",
        "hour",
    ]:
        if col not in total_clip_df.columns:
            total_clip_df[col] = None

    total_clip_df["hour"] = total_clip_df["created_at"].dt.hour
    total_clip_df["date"] = total_clip_df["created_at"].dt.date

    print(f"Created test data:")
    print(f"  - Total clips: {len(total_clip_df)}")
    print(f"  - Unique users: {total_clip_df['user_id'].nunique()}")
    print(f"  - Reactions: {len(reaction_df)}")
    print(f"  - Boosts: {len(boosts_action_df)}")
    print(f"  - Playlist entries: {len(playlist_clip_df)}")

    return total_clip_df, reaction_df, boosts_action_df, playlist_clip_df


def save_test_data(output_dir: str):
    """Save test data to files."""
    print(f"\nSaving test data to {output_dir}...")

    # Create output directory
    Path(output_dir).mkdir(parents=True, exist_ok=True)

    # Generate test data
    total_clip_df, reaction_df, boosts_action_df, playlist_clip_df = create_test_data()

    # Save as CSV files
    total_clip_df.to_csv(f"{output_dir}/total_clip_df.csv", index=False)
    reaction_df.to_csv(f"{output_dir}/reaction_df.csv", index=False)
    boosts_action_df.to_csv(f"{output_dir}/boosts_action_df.csv", index=False)
    playlist_clip_df.to_csv(f"{output_dir}/playlist_clip_df.csv", index=False)

    # Also save one as pickle to test pickle loading
    total_clip_df.to_pickle(f"{output_dir}/total_clip_df.pkl")

    print("Test data saved successfully!")

    return total_clip_df


def run_clustering_pipeline(input_dir: str, output_dir: str):
    """Run the clustering pipeline."""
    print(f"\nRunning clustering pipeline...")
    print(f"  Input: {input_dir}")
    print(f"  Output: {output_dir}")

    # Run the pipeline
    cmd = [
        sys.executable,
        "user_clustering.py",
        "--input-dir",
        input_dir,
        "--output-dir",
        output_dir,
        "--sample-size",
        "1000",  # Use sampling for faster test
    ]

    result = subprocess.run(cmd, capture_output=True, text=True)

    if result.returncode != 0:
        print("Pipeline failed!")
        print("STDOUT:", result.stdout)
        print("STDERR:", result.stderr)
        raise RuntimeError("Pipeline execution failed")

    print("Pipeline completed successfully!")


def validate_results(output_dir: str, total_clip_df: pd.DataFrame):
    """Validate the clustering results."""
    print(f"\nValidating results in {output_dir}...")

    # Load results
    assignments = pd.read_csv(f"{output_dir}/user_cluster_assignments.csv")
    profiles = pd.read_csv(f"{output_dir}/cluster_profiles.csv", index_col=0)

    with open(f"{output_dir}/cluster_type_mapping.json", "r") as f:
        cluster_mapping = json.load(f)

    print(f"\nCluster assignments:")
    print(f"  Total users clustered: {len(assignments)}")
    print(f"  Number of clusters: {assignments['cluster_label'].nunique()}")

    # Check cluster types
    print(f"\nCluster type mapping:")
    for cluster_id, cluster_type in cluster_mapping.items():
        count = len(assignments[assignments["cluster_label"] == int(cluster_id)])
        print(f"  Cluster {cluster_id} ({cluster_type}): {count} users")

    # Validate expected patterns
    print(f"\nValidating expected patterns...")

    # Check bot detection
    bot_clusters = [k for k, v in cluster_mapping.items() if v == "bot"]
    if bot_clusters:
        bot_cluster_id = int(bot_clusters[0])
        bot_users = assignments[assignments["cluster_label"] == bot_cluster_id][
            "user_id"
        ]
        bot_user_types = bot_users.str.split("_").str[0].value_counts()
        print(f"\nBot cluster contains:")
        print(bot_user_types)

        # Check bot characteristics
        bot_profile = profiles.loc[bot_cluster_id]
        assert (
            bot_profile["consumption_ratio"] < 0.1
        ), "Bot cluster should have low consumption"
        assert (
            bot_profile["creation_burst_score"] > 50
        ), "Bot cluster should have high burst score"
        print("✓ Bot cluster characteristics validated")

    # Check casual users
    casual_clusters = [k for k, v in cluster_mapping.items() if v == "casual"]
    if casual_clusters:
        casual_cluster_id = int(casual_clusters[0])
        casual_profile = profiles.loc[casual_cluster_id]
        assert (
            casual_profile["has_lyrics_generation"] > 30
        ), "Casual cluster should use lyrics generation"
        print("✓ Casual cluster characteristics validated")

    # Check pro users
    pro_clusters = [k for k, v in cluster_mapping.items() if v == "pro_serious"]
    if pro_clusters:
        pro_cluster_id = int(pro_clusters[0])
        pro_profile = profiles.loc[pro_cluster_id]
        assert (
            pro_profile["pro_user_ratio"] > 0.5
        ), "Pro cluster should have high pro ratio"
        print("✓ Pro cluster characteristics validated")

    # Load and display summary report
    print(f"\n{'='*60}")
    print("CLUSTERING SUMMARY REPORT:")
    print("=" * 60)
    with open(f"{output_dir}/clustering_summary_report.txt", "r") as f:
        print(f.read())

    print(f"\n{'='*60}")
    print("✓ All validations passed!")
    print("✓ Test completed successfully!")


def main():
    """Main test execution."""
    print("=" * 60)
    print("USER CLUSTERING PIPELINE - END-TO-END TEST")
    print("=" * 60)

    # Setup directories
    test_dir = "test_data"
    output_dir = "test_output"

    try:
        # Clean up previous test runs
        for dir_path in [test_dir, output_dir]:
            if os.path.exists(dir_path):
                shutil.rmtree(dir_path)

        # Step 1: Create and save test data
        total_clip_df = save_test_data(test_dir)

        # Step 2: Run clustering pipeline
        run_clustering_pipeline(test_dir, output_dir)

        # Step 3: Validate results
        validate_results(output_dir, total_clip_df)

    except Exception as e:
        print(f"\n❌ Test failed: {str(e)}")
        raise
    finally:
        # Cleanup (optional - comment out to inspect results)
        # for dir_path in [test_dir, output_dir]:
        #     if os.path.exists(dir_path):
        #         shutil.rmtree(dir_path)
        pass


if __name__ == "__main__":
    main()
