import os
import subprocess
from datetime import date, datetime, timedelta
from typing import Any, Optional

import requests
import typer
from pytz import timezone
from typer import Typer

app: Typer = typer.Typer()

AMERICA_NEW_YORK_TIMEZONE = "America/New_York"
DATETIME_FORMAT: str = "%B %d, %Y %I:%M %p"
ISSUES_PER_SECTION: int = 50
ISSUES_TO_FETCH: int = 100

REPO_OWNER = "zed-industries"
REPO_NAME = "zed"
GITHUB_API_BASE_URL = "https://api.github.com"

EXCLUDE_LABEL = "ignore top-ranking issues"


@app.command()
def main(
    github_token: Optional[str] = None,
    issue_reference_number: Optional[int] = None,
    query_day_interval: Optional[int] = None,
) -> None:
    script_start_time: datetime = datetime.now()
    start_date: date | None = None

    if query_day_interval:
        tz = timezone(AMERICA_NEW_YORK_TIMEZONE)
        today = datetime.now(tz).date()
        start_date = today - timedelta(days=query_day_interval)

    # GitHub Workflow will pass in the token as an argument,
    # but we can place it in our env when running the script locally, for convenience
    token = github_token or os.getenv("GITHUB_ACCESS_TOKEN")
    if not token:
        try:
            result = subprocess.run(
                ["gh", "auth", "token"], capture_output=True, text=True, check=True
            )
            token = result.stdout.strip()
        except (subprocess.CalledProcessError, FileNotFoundError):
            raise typer.BadParameter(
                "GitHub token is required. Pass --github-token, set GITHUB_ACCESS_TOKEN env var, or log in with `gh auth login`."
            )

    headers = {
        "Authorization": f"token {token}",
        "Accept": "application/vnd.github+json",
    }

    section_to_issues = get_section_to_issues(headers, start_date)
    issue_text: str = create_issue_text(section_to_issues)

    if issue_reference_number:
        update_reference_issue(headers, issue_reference_number, issue_text)
    else:
        print(issue_text)

    run_duration: timedelta = datetime.now() - script_start_time
    print(f"Ran for {run_duration}")


def get_section_to_issues(
    headers: dict[str, str], start_date: date | None = None
) -> dict[str, list[dict[str, Any]]]:
    """Fetch top-ranked issues for each section from GitHub."""

    section_filters = {
        "Bugs": "type:Bug",
        "Crashes": "type:Crash",
        "Features": "type:Feature",
        "Tracking issues": "type:Tracking",
        "Meta issues": "type:Meta",
        "Windows": 'label:"platform:windows"',
    }

    section_to_issues: dict[str, list[dict[str, Any]]] = {}
    for section, search_qualifier in section_filters.items():
        query_parts = [
            f"repo:{REPO_OWNER}/{REPO_NAME}",
            "is:issue",
            "is:open",
            f'-label:"{EXCLUDE_LABEL}"',
            search_qualifier,
        ]

        if start_date:
            query_parts.append(f"created:>={start_date.strftime('%Y-%m-%d')}")

        query = " ".join(query_parts)
        url = f"{GITHUB_API_BASE_URL}/search/issues"
        params = {
            "q": query,
            "sort": "reactions-+1",
            "order": "desc",
            "per_page": ISSUES_TO_FETCH,  # this will work as long as it's ≤ 100
        }

        # we are only fetching one page on purpose
        response = requests.get(url, headers=headers, params=params)
        response.raise_for_status()
        items = response.json()["items"]

        issues: list[dict[str, Any]] = []
        for item in items:
            reactions = item["reactions"]
            score = reactions["+1"] - reactions["-1"]
            if score > 0:
                issues.append(
                    {
                        "url": item["html_url"],
                        "score": score,
                        "created_at": item["created_at"],
                    }
                )

        if not issues:
            continue

        issues.sort(key=lambda x: (-x["score"], x["created_at"]))
        section_to_issues[section] = issues[:ISSUES_PER_SECTION]

    # Sort sections by total score (highest total first)
    section_to_issues = dict(
        sorted(
            section_to_issues.items(),
            key=lambda item: sum(issue["score"] for issue in item[1]),
            reverse=True,
        )
    )
    return section_to_issues


def update_reference_issue(
    headers: dict[str, str], issue_number: int, body: str
) -> None:
    url = f"{GITHUB_API_BASE_URL}/repos/{REPO_OWNER}/{REPO_NAME}/issues/{issue_number}"
    response = requests.patch(url, headers=headers, json={"body": body})
    response.raise_for_status()


def create_issue_text(section_to_issues: dict[str, list[dict[str, Any]]]) -> str:
    tz = timezone(AMERICA_NEW_YORK_TIMEZONE)
    current_datetime: str = datetime.now(tz).strftime(f"{DATETIME_FORMAT} (%Z)")

    lines: list[str] = [f"*Updated on {current_datetime}*"]

    for section, issues in section_to_issues.items():
        lines.append(f"\n## {section}\n")
        for i, issue in enumerate(issues):
            lines.append(f"{i + 1}. {issue['url']} ({issue['score']} :thumbsup:)")

    lines.append("\n---\n")
    lines.append(
        "*For details on how this issue is generated, "
        "[see the script](https://github.com/zed-industries/zed/blob/main/script/update_top_ranking_issues/main.py)*"
    )

    return "\n".join(lines)


if __name__ == "__main__":
    app()
