"""
Minimum version check for the M365 Copilot Agent Evaluation CLI.

Strategy:
  The published package.json contains a custom "minCliVersion" field.
  At runtime the CLI fetches the latest published package metadata via
  an aka.ms redirect that points to the npm registry API:

      https://aka.ms/m365-evals-min-version
        -> https://registry.npmjs.org/@microsoft/m365-copilot-eval/latest

  The response is the full published package.json as JSON. We simply
  read the "minCliVersion" field and compare it against the local
  version.

  Using aka.ms as an intermediary lets us change the target endpoint
  without shipping a new CLI version.
"""

import json
import os
import urllib.request
from typing import Optional

from packaging.version import Version

from cli_logging.cli_logger import emit_structured_log
from cli_logging.logging_utils import Operation

# The public npm package name
NPM_PACKAGE_NAME = "@microsoft/m365-copilot-eval"

# npm registry API URL via aka.ms redirect
MIN_VERSION_URL = "https://aka.ms/m365-evals-min-version"

# Timeout for fetching from the registry (seconds) — kept short to fail fast
FETCH_TIMEOUT = 3


def get_cli_version_string() -> Optional[str]:
    """
    Read the raw "version" string from package.json at the repo root.

    Unlike :func:`get_cli_version`, this returns the verbatim value (e.g.
    ``"1.1.1-preview.1"``) without parsing/normalizing it through
    ``packaging.version.Version`` — useful where the exact published string
    matters, such as the outbound ``User-Agent`` header.

    On failure a warning is emitted via the CLI logger (level-filtered, so it
    is suppressed at ``--log-level error``).

    Returns:
        The raw version string, or None if package.json cannot be read or
        has no "version" field.
    """
    try:
        # Walk up from this file (src/clients/cli/) to the repo root
        here = os.path.dirname(os.path.abspath(__file__))
        package_json_path = os.path.join(here, "..", "..", "..", "package.json")
        package_json_path = os.path.normpath(package_json_path)
        with open(package_json_path, "r", encoding="utf-8") as f:
            data = json.load(f)
        version = data.get("version")
        return version if version else None
    except Exception as e:
        emit_structured_log(
            "warning",
            f"Could not determine current CLI version: {e}",
            Operation.SETUP,
        )
        return None


def get_cli_version() -> Optional[Version]:
    """
    Read the current CLI version from package.json at the repo root.

    release-please keeps package.json's "version" field up to date,
    so there is no need for a hardcoded version constant.

    On failure a warning is emitted via the CLI logger (level-filtered).

    Returns:
        A parsed Version object (e.g. Version('1.1.1-preview.1')),
        or None if package.json cannot be read or the version is
        unparseable.
    """
    raw = get_cli_version_string()
    if raw is None:
        return None
    try:
        return Version(raw)
    except Exception as e:
        emit_structured_log(
            "warning",
            f"Could not determine current CLI version: {e}",
            Operation.SETUP,
        )
        return None


def fetch_min_version() -> Optional[Version]:
    """
    Fetch the minimum required CLI version from the npm registry.

    How it works:
      1. GET https://registry.npmjs.org/@microsoft/m365-copilot-eval/latest
      2. The response is the published package.json as plain JSON.
      3. Read the "minCliVersion" field.

    On failure a warning is emitted via the CLI logger (level-filtered).

    Returns:
        A parsed Version object (e.g. Version('1.3.0')),
        or None on any failure.
    """
    try:
        req = urllib.request.Request(
            MIN_VERSION_URL,
            headers={
                "Accept": "application/json",
            },
        )
        with urllib.request.urlopen(req, timeout=FETCH_TIMEOUT) as resp:
            data = json.loads(resp.read().decode("utf-8"))

        return Version(data.get("minCliVersion"))

    except Exception as e:
        emit_structured_log(
            "warning",
            f"Could not fetch minimum version from registry: {e}",
            Operation.SETUP,
        )
        return None


def check_min_version(current_version: Optional[Version]) -> bool:
    """
    Check whether the current CLI version meets the minimum required version
    published in the npm registry.

    Args:
        current_version: The parsed Version this CLI is running
                         (from get_cli_version()), or None if version
                         cannot be determined.

    Returns:
        True if the version is acceptable (>= minVersion or check failed
        gracefully), False if the current version is below the minimum.
    """
    # Fail open if current version unknown (package.json missing/unreadable)
    if current_version is None:
        return True

    min_version = fetch_min_version()

    if min_version is None:
        return True

    if current_version < min_version:
        emit_structured_log(
            "error",
            f"This version, {current_version}, of the M365 Evals CLI is no "
            f"longer functional; you must update to continue. Update by "
            f"running: npm install -g {NPM_PACKAGE_NAME}@latest",
            Operation.SETUP,
        )
        return False

    return True
