from datetime import datetime
import sys
from typing import Any, Optional

from jira import JIRA, JIRAError

from rhjira import util


def _fetch_sprints_for_project(jira: JIRA, project_key: str) -> list[dict[str, Any]]:
    """Fetch sprints for a project via the Agile REST API (boards -> sprints)."""
    base = jira._options["server"].rstrip("/")
    # Get boards for the project
    r = jira._session.get(
        f"{base}/rest/agile/1.0/board",
        params={"projectKeyOrId": project_key},
    )
    if not r.ok:
        raise JIRAError(status_code=r.status_code, response=r)
    boards = r.json().get("values", [])
    if not boards:
        return []
    seen: set[int] = set()
    sprints: list[dict[str, Any]] = []
    for board in boards:
        start_at = 0
        while True:
            r2 = jira._session.get(
                f"{base}/rest/agile/1.0/board/{board['id']}/sprint",
                params={"startAt": start_at, "maxResults": 50},
            )
            if not r2.ok:
                raise JIRAError(status_code=r2.status_code, response=r2)
            data = r2.json()
            for s in data.get("values", []):
                sid = s.get("id")
                if sid is not None and sid not in seen:
                    seen.add(sid)
                    sprints.append({
                        "id": sid,
                        "name": s.get("name", ""),
                        "state": s.get("state", "").lower(),
                        "startDate": s.get("startDate"),
                        "endDate": s.get("endDate"),
                    })
            if data.get("isLast", True):
                break
            start_at = data.get("startAt", 0) + len(data.get("values", []))
    # Order: active, then future (startDate asc), then closed (endDate desc, most recent first)
    def _parse_iso(iso: str | None) -> float:
        if not iso:
            return 0.0
        try:
            # Handle Z suffix and optional fractional seconds
            s = (iso or "").replace("Z", "+00:00")
            return datetime.fromisoformat(s).timestamp()
        except (ValueError, TypeError):
            return 0.0

    def sort_key(s: dict[str, Any]) -> tuple[int, float]:
        state = s.get("state", "")
        if state == "active":
            return (0, _parse_iso(s.get("startDate")))
        if state == "future":
            return (1, _parse_iso(s.get("startDate")))
        # Closed: use negative endDate timestamp so most recent sorts first
        return (2, -_parse_iso(s.get("endDate")))
    sprints.sort(key=sort_key)
    return sprints


def get_sprint_id(jira: JIRA, project_key: str, value: str) -> Optional[int]:
    """Resolve sprint name or id to sprint id for the project. None if empty or not found."""
    v = (value or "").strip()
    if not v:
        return None
    if v.isdigit():
        return int(v)
    try:
        sprints = _fetch_sprints_for_project(jira, project_key)
        for s in sprints:
            if s.get("name", "").strip() == v:
                return s.get("id")
    except JIRAError:
        pass
    return None


def get_project_info(jira: JIRA, project_key: str, info_type: str) -> list[dict[str, Any]]:
    try:
        if info_type == "components":
            components = jira.project_components(project_key)
            return [{"name": c.name} for c in components]
        elif info_type == "resolutions":
            # Resolutions are global, not per-project, but for consistency, we fetch them here
            resolutions = jira.resolutions()
            return [{"name": r.name, "description": getattr(r, "description", "")} for r in resolutions] # noqa: E501
        elif info_type == "securitylevels":
            # Get security levels for the project by fetching an issue's editmeta
            # First, search for any issue in the project
            issues = jira.search_issues(f"project={project_key}", maxResults=1)
            if issues and len(issues) > 0:
                # Get editmeta for the first issue
                editmeta = jira.editmeta(issues[0].key)
                if 'fields' in editmeta and 'security' in editmeta['fields']:
                    security_field = editmeta['fields']['security']
                    if 'allowedValues' in security_field:
                        return [{"name": level.get("name", ""), "description": level.get("description", "")} for level in security_field['allowedValues']] # noqa: E501
            return []
        elif info_type == "sprints":
            return _fetch_sprints_for_project(jira, project_key)
        else:  # versions
            versions = jira.project_versions(project_key)
            return [{"name": v.name} for v in versions]
    except JIRAError as e:
        util.handle_jira_error(e, f"fetch {info_type} for project {project_key}")
        sys.exit(1)


def display_components(components: list[dict[str, Any]]) -> None:
    if components:
        for c in components:
            print(c["name"])
    else:
        print("No components found.")


def display_versions(versions: list[dict[str, Any]]) -> None:
    if versions:
        for v in versions:
            print(v["name"])
    else:
        print("No versions found.")


def display_resolutions(resolutions: list[dict[str, Any]]) -> None:
    if resolutions:
        for r in resolutions:
            desc = f" - {r['description']}" if r.get('description') else ""
            print(f"{r['name']}{desc}")
    else:
        print("No resolutions found.")


def display_securitylevels(securitylevels: list[dict[str, Any]]) -> None:
    if securitylevels:
        for s in securitylevels:
            desc = f" - {s['description']}" if s.get('description') else ""
            print(f"{s['name']}{desc}")
    else:
        print("No security levels found.")


def display_sprints(sprints: list[dict[str, Any]]) -> None:
    if sprints:
        for s in sprints:
            name = s.get("name", "")
            if s.get("state") == "active":
                name = f"{name} (current)"
            elif s.get("state") == "closed":
                name = f"{name} (closed)"
            print(name)
    else:
        print("No sprints found.")


def info(jira: JIRA) -> None:
    # handle arguments
    sys.argv.remove("info")

    if len(sys.argv) != 4 or sys.argv[1] != "--project":
        print("Usage: rhjira info --project <project> <components|versions|resolutions|securitylevels|sprints>") # noqa: E501
        sys.exit(1)

    project = sys.argv[2]
    info_type = sys.argv[3]

    if info_type not in ["components", "versions", "resolutions", "securitylevels", "sprints"]:
        print("Error: info type must be one of 'components', 'versions', 'resolutions', 'securitylevels', 'sprints'") # noqa: E501
        sys.exit(1)

    info_list = get_project_info(jira, project, info_type)

    if info_type == "components":
        display_components(info_list)
    elif info_type == "versions":
        display_versions(info_list)
    elif info_type == "resolutions":
        display_resolutions(info_list)
    elif info_type == "sprints":
        display_sprints(info_list)
    else:
        display_securitylevels(info_list)
