#!/usr/bin/env python3
"""
ICPC Judge CLI - Standalone submission tool.
Submits competitive programming solutions to the ICPC Judge and streams the result in real time.
No external dependencies required (uses standard Python library).
"""

import argparse
import base64
import getpass
import json
import os
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path

CONFIG_FILE = Path.home() / ".icpc_judge_config"

# ANSI color escape codes
COLOR_RESET = "\033[0m"
COLOR_BOLD = "\033[1m"
COLOR_GREEN = "\033[1;32m"
COLOR_RED = "\033[1;31m"
COLOR_YELLOW = "\033[1;33m"
COLOR_BLUE = "\033[1;34m"
COLOR_CYAN = "\033[1;36m"

if not sys.stdout.isatty():
    COLOR_RESET = COLOR_BOLD = COLOR_GREEN = COLOR_RED = COLOR_YELLOW = COLOR_BLUE = COLOR_CYAN = ""


def load_config():
    """Loads configuration from ~/.icpc_judge_config if present."""
    if CONFIG_FILE.is_file():
        try:
            with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                return json.load(f)
        except Exception:
            pass
    return {}


def save_config(server, username, password=""):
    """Saves credentials and server URL to ~/.icpc_judge_config securely."""
    data = {"server": server, "user": username}
    if password:
        data["password"] = password
    try:
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2)
        os.chmod(CONFIG_FILE, 0o600)
        print(f"{COLOR_GREEN}✔ Saved configuration to {CONFIG_FILE}{COLOR_RESET}")
    except Exception as e:
        print(f"{COLOR_YELLOW}Warning: Could not save configuration: {e}{COLOR_RESET}")


def detect_language(file_path):
    """Auto-detects language code from source file extension."""
    suffix = Path(file_path).suffix.lower()
    ext_map = {
        ".cpp": "cc",
        ".cc": "cc",
        ".cxx": "cc",
        ".c++": "cc",
        ".c": "c",
        ".py": "py",
        ".java": "java",
    }
    return ext_map.get(suffix)


def make_request(url, method="GET", data=None, headers=None):
    """Executes HTTP request using standard library urllib."""
    req = urllib.request.Request(url, method=method)
    if headers:
        for k, v in headers.items():
            req.add_header(k, v)
    if data:
        req.data = data
    return urllib.request.urlopen(req, timeout=30)


def main():
    parser = argparse.ArgumentParser(
        description="Submit solutions to the ICPC Judge platform and monitor results."
    )
    parser.add_argument("problem", help="Problem ID (e.g., 10051 or chocolatechip)")
    parser.add_argument("file", help="Path to solution source code file")
    parser.add_argument("-s", "--server", help="Judge server URL (default: http://localhost:8000)")
    parser.add_argument("-u", "--user", help="Username or email")
    parser.add_argument("-p", "--password", help="Password")
    parser.add_argument("-l", "--language", help="Language override (c, cc, py, java)")
    parser.add_argument("--save-config", action="store_true", help="Save server and user credentials to ~/.icpc_judge_config")
    parser.add_argument("--timeout", type=int, default=120, help="Maximum seconds to wait for verdict (default: 120)")

    args = parser.parse_args()

    # 1. Resolve source file
    src_file = Path(args.file)
    if not src_file.is_file():
        print(f"{COLOR_RED}Error: Source file '{args.file}' does not exist.{COLOR_RESET}", file=sys.stderr)
        sys.exit(2)

    file_size = src_file.stat().st_size
    if file_size > 256 * 1024:
        print(f"{COLOR_RED}Error: File size ({file_size / 1024:.1f} KB) exceeds 256 KB limit.{COLOR_RESET}", file=sys.stderr)
        sys.exit(2)

    # 2. Resolve language
    lang = args.language or detect_language(src_file)
    if not lang:
        print(f"{COLOR_RED}Error: Could not determine language for '{src_file.name}'. Use -l/--language (c, cc, py, java).{COLOR_RESET}", file=sys.stderr)
        sys.exit(2)

    # 3. Resolve configuration and credentials
    config = load_config()
    server = (args.server or os.environ.get("ICPC_SERVER") or config.get("server") or "http://localhost:8000").rstrip("/")
    user = args.user or os.environ.get("ICPC_USER") or config.get("user")
    password = args.password or os.environ.get("ICPC_PASSWORD") or config.get("password")

    if not user:
        try:
            user = input("Username or Email: ").strip()
        except (KeyboardInterrupt, EOFError):
            print("\nAborted.")
            sys.exit(1)

    if not password:
        try:
            password = getpass.getpass("Password: ")
        except (KeyboardInterrupt, EOFError):
            print("\nAborted.")
            sys.exit(1)

    if args.save_config:
        save_config(server, user, password)

    # 4. Prepare Basic Auth Header
    creds = f"{user}:{password}"
    b64_creds = base64.b64encode(creds.encode("utf-8")).decode("ascii")
    auth_header = f"Basic {b64_creds}"

    # 5. Read source code
    code_content = src_file.read_text(encoding="utf-8", errors="replace")

    submit_url = f"{server}/problems/api/submit/"
    headers = {
        "Authorization": auth_header,
        "Content-Type": "application/json",
        "User-Agent": "ICPC-Judge-CLI/1.0",
    }
    payload = json.dumps({
        "problem_id": args.problem,
        "language": lang,
        "code": code_content,
    }).encode("utf-8")

    print(f"{COLOR_BLUE}Submitting '{src_file.name}' for problem '{args.problem}' ({lang})...{COLOR_RESET}")

    try:
        resp = make_request(submit_url, method="POST", data=payload, headers=headers)
        submit_data = json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        err_body = e.read().decode("utf-8", errors="replace")
        try:
            err_json = json.loads(err_body)
            msg = err_json.get("error") or err_json.get("message") or err_body
        except Exception:
            if "<!DOCTYPE" in err_body or "<html" in err_body:
                if e.code == 404:
                    msg = f"API endpoint not found on server ({submit_url}). The server container needs to be rebuilt with the latest code."
                else:
                    msg = f"Server returned an HTML error page (HTTP {e.code})."
            else:
                msg = err_body
        print(f"{COLOR_RED}Submission failed (HTTP {e.code}): {msg}{COLOR_RESET}", file=sys.stderr)
        sys.exit(1)
    except Exception as e:
        print(f"{COLOR_RED}Network connection error: {e}{COLOR_RESET}", file=sys.stderr)
        sys.exit(1)

    sub_id = submit_data.get("submission_id")
    pname = submit_data.get("problem_name", args.problem)
    print(f"{COLOR_GREEN}✔ Submission #{sub_id} received: {pname}{COLOR_RESET}")
    print(f"Waiting for judging verdict...", end="", flush=True)

    # 6. Polling loop
    status_url = f"{server}/problems/api/submissions/{sub_id}/status/"
    poll_start = time.time()
    spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
    idx = 0

    while True:
        if time.time() - poll_start > args.timeout:
            print(f"\n{COLOR_YELLOW}Timed out waiting for verdict after {args.timeout}s. Check status later.{COLOR_RESET}")
            sys.exit(2)

        try:
            resp = make_request(status_url, method="GET", headers=headers)
            status_data = json.loads(resp.read().decode("utf-8"))
        except Exception:
            time.sleep(1)
            continue

        if status_data.get("is_judged"):
            outcome = status_data.get("outcome")
            display = status_data.get("outcome_display")
            runtime = status_data.get("runtime")
            runtime_str = f" ({runtime:.3f}s)" if runtime is not None else ""

            # Clear line and print final verdict
            print("\r" + " " * 60 + "\r", end="")

            if outcome == "A":
                print(f"{COLOR_GREEN}{COLOR_BOLD}✔ ACCEPTED{COLOR_RESET}{COLOR_GREEN}{runtime_str}{COLOR_RESET}")
                sys.exit(0)
            elif outcome == "W":
                print(f"{COLOR_RED}{COLOR_BOLD}✖ WRONG ANSWER{COLOR_RESET}{COLOR_RED}{runtime_str}{COLOR_RESET}")
                sys.exit(1)
            elif outcome == "T":
                print(f"{COLOR_RED}{COLOR_BOLD}✖ TIME LIMIT EXCEEDED{COLOR_RESET}{COLOR_RED}{runtime_str}{COLOR_RESET}")
                sys.exit(1)
            elif outcome == "R":
                print(f"{COLOR_RED}{COLOR_BOLD}✖ RUNTIME ERROR{COLOR_RESET}{COLOR_RED}{runtime_str}{COLOR_RESET}")
                sys.exit(1)
            elif outcome == "C":
                print(f"{COLOR_YELLOW}{COLOR_BOLD}✖ COMPILE ERROR{COLOR_RESET}")
                excerpt = status_data.get("log_excerpt")
                if excerpt:
                    print(f"{COLOR_YELLOW}--- Compiler Output ---{COLOR_RESET}")
                    print(excerpt)
                    print(f"{COLOR_YELLOW}-----------------------{COLOR_RESET}")
                sys.exit(1)
            else:
                print(f"{COLOR_RED}{COLOR_BOLD}✖ {display.upper()}{COLOR_RESET}")
                sys.exit(1)

        spin_char = spinner[idx % len(spinner)]
        idx += 1
        elapsed_sec = int(time.time() - poll_start)
        print(f"\rJudging... {spin_char} ({elapsed_sec}s)", end="", flush=True)
        time.sleep(1)


if __name__ == "__main__":
    main()
