"""
Command-line interface for managing and running the Pearmut server.
"""

import argparse
import hashlib
import json
import os
import urllib.parse

import psutil

from .utils import ROOT, load_progress_data

os.makedirs(f"{ROOT}/data/tasks", exist_ok=True)
load_progress_data(warn=None)


def _run(args_unknown):
    import uvicorn

    from .app import app, tasks_data

    args = argparse.ArgumentParser()
    args.add_argument(
        "--port", type=int, default=8001,
        help="Port to run the server on"
    )
    args.add_argument(
        "--server", default="http://localhost:8001",
        help="Prefix server URL for protocol links"
    )
    args = args.parse_args(args_unknown)

    # print access dashboard URL for all campaigns
    if tasks_data:
        print(
            args.server + "/dashboard.html?" + "&".join([
                f"campaign_id={urllib.parse.quote_plus(campaign_id)}&token={campaign_data["token"]}"
                for campaign_id, campaign_data in tasks_data.items()
            ])
        )

    uvicorn.run(
        app,
        host="127.0.0.1",
        port=args.port,
        reload=False,
        # log_level="info",
    )


def _add_campaign(args_unknown):
    """
    Add a new campaign from a JSON data file.
    """
    import random

    import wonderwords

    args = argparse.ArgumentParser()
    args.add_argument(
        'data_file', type=str,
        help='Path to the campaign data file'
    )
    args.add_argument(
        "-o", "--overwrite", action="store_true",
        help="Overwrite existing campaign if it exists"
    )
    args.add_argument(
        "--server", default="http://localhost:8001",
        help="Prefix server URL for protocol links"
    )
    args = args.parse_args(args_unknown)

    with open(args.data_file, 'r') as f:
        campaign_data = json.load(f)

    with open(f"{ROOT}/data/progress.json", "r") as f:
        progress_data = json.load(f)

    if campaign_data['campaign_id'] in progress_data and not args.overwrite:
        print(
            f"Campaign {campaign_data['campaign_id']} already exists.",
            "Use -o to overwrite."
        )
        exit(1)

    if "info" not in campaign_data:
        raise ValueError("Campaign data must contain 'info' field.")
    if "data" not in campaign_data:
        raise ValueError("Campaign data must contain 'data' field.")
    if "assignment" not in campaign_data["info"]:
        raise ValueError("Campaign 'info' must contain 'assignment' field.")
    if "template" not in campaign_data["info"]:
        raise ValueError("Campaign 'info' must contain 'template' field.")

    assignment = campaign_data["info"]["assignment"]
    # use random words for identifying users
    rng = random.Random(campaign_data["campaign_id"])
    rword = wonderwords.RandomWord(rng=rng)
    if assignment == "task-based":
        tasks = campaign_data["data"]
        if not isinstance(tasks, list):
            raise ValueError(
                "Task-based campaign 'data' must be a list of tasks.")
        if not all(isinstance(task, list) for task in tasks):
            raise ValueError(
                "Each task in task-based campaign 'data' must be a list of items.")
        num_users = len(tasks)
    elif assignment == "single-stream":
        tasks = campaign_data["data"]
        if "num_users" not in campaign_data["info"]:
            raise ValueError(
                "Single-stream campaigns must specify 'num_users' in info.")
        if not isinstance(campaign_data["data"], list):
            raise ValueError(
                "Single-stream campaign 'data' must be a list of items.")
        num_users = campaign_data["info"]["num_users"]
    elif assignment == "dynamic":
        raise NotImplementedError(
            "Dynamic campaign assignment is not yet implemented.")
    else:
        raise ValueError(f"Unknown campaign assignment type: {assignment}")

    user_ids = []
    while len(user_ids) < num_users:
        # generate random user IDs
        new_id = f"{rword.random_words(amount=1, include_parts_of_speech=['adjective'])[0]}-{rword.random_words(amount=1, include_parts_of_speech=['noun'])[0]}"
        if new_id not in user_ids:
            user_ids.append(new_id)
    user_ids = [
        f"{user_id}-{rng.randint(0, 999):03d}"
        for user_id in user_ids
    ]

    # For task-based, data is a dict mapping user_id -> tasks
    # For single-stream, data is a flat list (shared among all users)
    if assignment == "task-based":
        campaign_data["data"] = {
            user_id: task
            for user_id, task in zip(user_ids, tasks)
        }
    elif assignment == "single-stream":
        campaign_data["data"] = tasks

    # generate a token for dashboard access if not present
    if "token" not in campaign_data:
        campaign_data["token"] = (
            hashlib.sha256(random.randbytes(16)).hexdigest()[:10]
        )

    user_progress = {
        user_id: {
            # TODO: progress tracking could be based on the assignment type
            "progress": (
                [False]*len(campaign_data["data"][user_id]) if assignment == "task-based"
                else [False]*len(campaign_data["data"]) if assignment == "single-stream"
                else []
            ),
            "time_start": None,
            "time_end": None,
            "time": 0,
            "url": (
                f"{args.server}/{campaign_data["info"]["template"]}.html"
                f"?campaign_id={urllib.parse.quote_plus(campaign_data['campaign_id'])}"
                f"&user_id={user_id}"
            ),
            "token_correct": hashlib.sha256(random.randbytes(16)).hexdigest()[:10],
            "token_incorrect": hashlib.sha256(random.randbytes(16)).hexdigest()[:10],
        }
        for user_id in user_ids
    }

    with open(f"{ROOT}/data/tasks/{campaign_data['campaign_id']}.json", "w") as f:
        json.dump(campaign_data, f, indent=2, ensure_ascii=False)

    progress_data[campaign_data['campaign_id']] = user_progress

    with open(f"{ROOT}/data/progress.json", "w") as f:
        json.dump(progress_data, f, indent=2, ensure_ascii=False)

    print(
        f"{args.server}/dashboard.html"
        f"?campaign_id={urllib.parse.quote_plus(campaign_data['campaign_id'])}"
        f"&token={campaign_data['token']}"
    )
    print("-"*10)
    for user_id, user_val in user_progress.items():
        # point to the protocol URL
        print(user_val["url"])


def main():
    """
    Main entry point for the CLI.
    """
    args = argparse.ArgumentParser()
    args.add_argument('command', type=str, choices=['run', 'add', 'purge'])
    args, args_unknown = args.parse_known_args()

    # enforce that only one pearmut process is running
    for p in psutil.process_iter():
        if "pearmut" == p.name() and p.pid != os.getpid():
            print("Exit all running pearmut processes before running more commands.")
            print(p)
            exit(1)

    if args.command == 'run':
        _run(args_unknown)
    elif args.command == 'add':
        _add_campaign(args_unknown)
    elif args.command == 'purge':
        import shutil

        confirm = input(
            "Are you sure you want to purge all campaign data? This action cannot be undone. [y/n] "
        )
        if confirm.lower() == 'y':
            shutil.rmtree(f"{ROOT}/data/tasks", ignore_errors=True)
            shutil.rmtree(f"{ROOT}/data/outputs", ignore_errors=True)
            if os.path.exists(f"{ROOT}/data/progress.json"):
                os.remove(f"{ROOT}/data/progress.json")
            print("All campaign data purged.")
        else:
            print("Cancelled.")
