# Copyright (c) Microsoft. All rights reserved.

import argparse
import sys

import wandb


def parse_args():
    parser = argparse.ArgumentParser(description="Validate a Weights & Biases run for reward/trace rollouts.")
    parser.add_argument("project", help="W&B project name")
    parser.add_argument("run_name", help="W&B run display name")
    parser.add_argument(
        "--reward-tolerance",
        type=int,
        default=0,
        help="Allowed difference between first and last val/n_rollouts_w_reward",
    )
    parser.add_argument(
        "--trace-tolerance",
        type=int,
        default=0,
        help="Allowed difference between first and last val/n_rollouts_w_trace",
    )
    return parser.parse_args()


args = parse_args()

project = args.project
run_name = args.run_name
api = wandb.Api()
entity_name = api.default_entity
print("Default entity:", entity_name)
print("Project:", project)
print("Run name:", run_name)

runs = api.runs(f"{entity_name}/{project}", filters={"displayName": run_name})
for run in runs:
    print(f"Found run: {run.name} (ID: {run.id})")
    if run.name == run_name:
        break
else:
    print(f"::error::Run with name '{run_name}' not found in project '{project}'.")
    sys.exit(1)

hist = run.history(
    keys=["val/reward", "val/n_rollouts_w_reward", "val/n_rollouts_w_trace", "val/mean_response_length"], pandas=True
)
print("History:", hist)
if hist.empty:
    print("::error::No history found for the run.")
    sys.exit(1)
else:
    # Check whether all rollouts have (approximately) succeeded
    first_row = hist.iloc[0]
    last_row = hist.iloc[-1]

    first_reward_rollouts = first_row["val/n_rollouts_w_reward"]
    last_reward_rollouts = last_row["val/n_rollouts_w_reward"]
    reward_diff = abs(first_reward_rollouts - last_reward_rollouts)

    if reward_diff > args.reward_tolerance or (first_reward_rollouts == 0 and last_reward_rollouts == 0):
        print(
            "::error::Some rollouts have failed to produce rewards: "
            f"{first_reward_rollouts} -> {last_reward_rollouts} "
            f"(tolerance={args.reward_tolerance})"
        )
        sys.exit(1)
    elif first_reward_rollouts != last_reward_rollouts:
        print(
            "::warning::First and last val/n_rollouts_w_reward are different: "
            f"{first_reward_rollouts} -> {last_reward_rollouts}"
        )

    first_trace_rollouts = first_row["val/n_rollouts_w_trace"]
    last_trace_rollouts = last_row["val/n_rollouts_w_trace"]
    trace_diff = abs(first_trace_rollouts - last_trace_rollouts)

    if trace_diff > args.trace_tolerance or (first_trace_rollouts == 0 and last_trace_rollouts == 0):
        print(
            "::error::Some rollouts have failed to produce traces: "
            f"{first_trace_rollouts} -> {last_trace_rollouts} "
            f"(tolerance={args.trace_tolerance})"
        )
        sys.exit(1)
    elif first_trace_rollouts != last_trace_rollouts:
        print(
            "::warning::First and last val/n_rollouts_w_trace are different: "
            f"{first_trace_rollouts} -> {last_trace_rollouts}"
        )

    val_mean_response = last_row["val/mean_response_length"]
    if val_mean_response < 1:
        print(f"::error::Mean response length is too short: {val_mean_response} (expected >= 1)")
        sys.exit(1)

    first_reward, last_reward = first_row["val/reward"], last_row["val/reward"]
    if last_reward <= first_reward:
        print(
            f"::warning title=Training no improvement::No improvement (run_name={run_name} start={first_reward:.4f}, end={last_reward:.4f})"
        )
    else:
        print(
            f"::notice title=Training completed::Run has improved (run_name={run_name} start={first_reward:.4f}, end={last_reward:.4f})"
        )
