#!/usr/bin/python3
"""Tests for slpkg.repo_info.RepoInfo."""

import json
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest
import requests

from slpkg.repo_info import RepoInfo


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def make_repo_info(
    *,
    repository: str = "sbo",
    option_repository: str | bool = False,
    option_fetch: bool = False,
    option_json: bool = False,
    columns: int = 80,
) -> RepoInfo:
    """Construct RepoInfo bypassing __init__ (avoids config_load/Repositories)."""
    obj: RepoInfo = object.__new__(RepoInfo)
    obj.repository = repository
    obj.cyan = ""
    obj.green = ""
    obj.red = ""
    obj.grey = ""
    obj.endc = ""
    obj.columns = columns
    obj.name_alignment = max(columns - 61, 1)
    obj.mirror_alignment = max(columns - 32, 1)
    obj.enabled = 0
    obj.total_packages = 0
    obj.repo_data = {}
    obj.dates = {}
    obj.mirrors_score = {}
    obj.option_for_repository = option_repository
    obj.option_for_fetch = option_fetch
    obj.option_json = option_json
    obj.load_data = MagicMock()
    obj.utils = MagicMock()

    repos = MagicMock()
    repos.sbo_repo_name = "sbo"
    repos.ponce_repo_name = "ponce"
    repos.sbo_git_mirror = "https://gitlab.com/SlackBuilds.org/slackbuilds.git"
    repos.ponce_git_mirror = "https://git.ponce.cc/ponce/slackbuilds.git"
    repos.default_repository = "sbo"
    repos.repos_information = "repos_info.json"
    repos.repositories_path = Path("/var/lib/slpkg/repos")
    repos.repositories = {
        "sbo": {"enable": True, "mirror_changelog": "https://slackbuilds.org"},
        "slack": {"enable": True, "mirror_changelog": "https://mirrors.slackware.com"},
        "alien": {"enable": False, "mirror_changelog": "https://alien.slackware.com"},
    }
    obj.repos = repos
    return obj


# ---------------------------------------------------------------------------
# repo_information
# ---------------------------------------------------------------------------


def test_repo_information_file_missing_returns_empty(tmp_path: Path) -> None:
    obj = make_repo_info()
    obj.repos.repositories_path = tmp_path
    obj.repos.repos_information = "repos_info.json"
    result = obj.repo_information()
    assert result == {}


def test_repo_information_file_present_returns_data(tmp_path: Path) -> None:
    info_file = tmp_path / "repos_info.json"
    obj = make_repo_info()
    obj.repos.repositories_path = tmp_path
    obj.repos.repos_information = "repos_info.json"
    obj.utils.read_json_file.return_value = {"sbo": {"last_updated": "2026-01-01"}}
    info_file.touch()
    result = obj.repo_information()
    assert "sbo" in result


# ---------------------------------------------------------------------------
# view_the_title
# ---------------------------------------------------------------------------


def test_view_the_title_single_repo(capsys) -> None:
    obj = make_repo_info(option_repository="sbo")
    obj.view_the_title()
    out = capsys.readouterr().out
    assert "Repository Information" in out


def test_view_the_title_all_repos(capsys) -> None:
    obj = make_repo_info(option_repository=False)
    obj.view_the_title()
    out = capsys.readouterr().out
    assert "Repositories Information" in out


# ---------------------------------------------------------------------------
# view_the_score_title
# ---------------------------------------------------------------------------


def test_view_the_score_title_prints_headers(capsys) -> None:
    obj = make_repo_info()
    obj.view_the_score_title()
    out = capsys.readouterr().out
    assert "Name:" in out
    assert "Mirror:" in out
    assert "Score:" in out


# ---------------------------------------------------------------------------
# view_the_line_information
# ---------------------------------------------------------------------------


def test_view_the_line_information_enabled_shows_status(capsys) -> None:
    obj = make_repo_info()
    obj.view_the_line_information(
        {"repo": "sbo", "date": "2026-01-01", "count": 10, "color": "", "status": "Enabled"}
    )
    out = capsys.readouterr().out
    assert "sbo" in out
    assert "Enabled" in out
    assert "2026-01-01" in out


def test_view_the_line_information_default_repo_shows_star(capsys) -> None:
    obj = make_repo_info()
    obj.view_the_line_information(
        {"repo": "sbo", "date": "-", "count": 0, "color": "", "status": "Enabled"}
    )
    out = capsys.readouterr().out
    assert "sbo *" in out


def test_view_the_line_information_non_default_no_star(capsys) -> None:
    obj = make_repo_info()
    obj.view_the_line_information(
        {"repo": "slack", "date": "-", "count": 0, "color": "", "status": "Enabled"}
    )
    out = capsys.readouterr().out
    assert "slack *" not in out


# ---------------------------------------------------------------------------
# view_summary_of_repository
# ---------------------------------------------------------------------------


def test_view_summary_of_repository_shows_total_packages(capsys) -> None:
    obj = make_repo_info(option_fetch=False)
    obj.total_packages = 42
    obj.view_summary_of_repository()
    out = capsys.readouterr().out
    assert "42" in out


def test_view_summary_of_repository_fetch_shows_score(capsys) -> None:
    obj = make_repo_info(option_fetch=True)
    obj.mirrors_score = {"sbo": 123}
    obj.view_summary_of_repository()
    out = capsys.readouterr().out
    assert "123" in out


# ---------------------------------------------------------------------------
# view_summary_of_all_repositories
# ---------------------------------------------------------------------------


def test_view_summary_of_all_repositories_shows_enabled_count(capsys) -> None:
    obj = make_repo_info(option_fetch=False)
    obj.enabled = 2
    obj.total_packages = 100
    obj.view_summary_of_all_repositories()
    out = capsys.readouterr().out
    assert "2/3" in out
    assert "100" in out


def test_view_summary_of_all_repositories_fetch_shows_fastest_slowest(capsys) -> None:
    obj = make_repo_info(option_fetch=True)
    obj.mirrors_score = {"sbo": 50, "slack": 200}
    obj.view_summary_of_all_repositories()
    out = capsys.readouterr().out
    assert "sbo" in out
    assert "slack" in out
    assert "Fastest" in out


def test_view_summary_of_all_repositories_fetch_no_scores_shows_counts(capsys) -> None:
    obj = make_repo_info(option_fetch=True)
    obj.mirrors_score = {}
    obj.enabled = 1
    obj.total_packages = 5
    obj.view_summary_of_all_repositories()
    out = capsys.readouterr().out
    assert "1/3" in out


# ---------------------------------------------------------------------------
# check_mirror_speed
# ---------------------------------------------------------------------------


def test_check_mirror_speed_success_records_score(capsys) -> None:
    obj = make_repo_info()
    mock_response = MagicMock()
    mock_response.status_code = 200
    with patch("requests.get", return_value=mock_response):
        obj.check_mirror_speed("sbo", "https://slackbuilds.org")
    assert "sbo" in obj.mirrors_score
    assert obj.mirrors_score["sbo"] >= 0


def test_check_mirror_speed_timeout_prints_timeout(capsys) -> None:
    obj = make_repo_info()
    with patch("requests.get", side_effect=requests.exceptions.Timeout):
        obj.check_mirror_speed("sbo", "https://slackbuilds.org")
    out = capsys.readouterr().out
    assert "Timeout" in out


def test_check_mirror_speed_connection_error_prints_error(capsys) -> None:
    obj = make_repo_info()
    with patch("requests.get", side_effect=requests.exceptions.ConnectionError):
        obj.check_mirror_speed("sbo", "https://slackbuilds.org")
    out = capsys.readouterr().out
    assert "Connection Error" in out


def test_check_mirror_speed_url_truncated_when_too_long(capsys) -> None:
    obj = make_repo_info(columns=80)
    long_url = "https://" + "x" * 100 + ".org"
    mock_response = MagicMock()
    mock_response.status_code = 200
    with patch("requests.get", return_value=mock_response):
        obj.check_mirror_speed("sbo", long_url)
    out = capsys.readouterr().out
    assert "..." in out


# ---------------------------------------------------------------------------
# _view_json()
# ---------------------------------------------------------------------------


def _make_json_repo_info(*, option_repository: str | bool = False) -> RepoInfo:
    """RepoInfo with option_json=True and load_repo_data pre-stubbed."""
    obj = make_repo_info(option_json=True, option_repository=option_repository)
    obj.repos.repositories = {
        "sbo": {"enable": True, "mirror_packages": "https://slackbuilds.org/", "mirror_changelog": ""},
        "alien": {"enable": False, "mirror_packages": "https://alien.slackware.com/", "mirror_changelog": ""},
    }
    obj.repos.default_repository = "sbo"
    obj.dates = {
        "sbo": {"last_updated": "2026-03-01"},
        "alien": {"last_updated": "2026-02-15"},
    }
    # Simulate loaded repo data (all repos).
    obj.repo_data = {
        "sbo": {f"pkg{i}": {} for i in range(5)},
        "alien": {f"pkg{i}": {} for i in range(3)},
    }
    obj.load_data = MagicMock()
    # Patch load_repo_data so it doesn't touch the filesystem.
    return obj


def test_view_json_is_valid_json(capsys) -> None:
    obj = _make_json_repo_info()
    with patch.object(obj, "load_repo_data"):
        obj._view_json()
    data = json.loads(capsys.readouterr().out)
    assert isinstance(data, list)


def test_view_json_entry_count(capsys) -> None:
    obj = _make_json_repo_info()
    with patch.object(obj, "load_repo_data"):
        obj._view_json()
    data = json.loads(capsys.readouterr().out)
    assert len(data) == 2


def test_view_json_contains_expected_keys(capsys) -> None:
    obj = _make_json_repo_info()
    with patch.object(obj, "load_repo_data"):
        obj._view_json()
    data = json.loads(capsys.readouterr().out)
    for item in data:
        for key in ("name", "enabled", "default", "last_updated", "packages", "mirror"):
            assert key in item, f"Missing key: {key}"


def test_view_json_enabled_field_is_bool(capsys) -> None:
    obj = _make_json_repo_info()
    with patch.object(obj, "load_repo_data"):
        obj._view_json()
    data = json.loads(capsys.readouterr().out)
    for item in data:
        assert isinstance(item["enabled"], bool)


def test_view_json_default_field_correct(capsys) -> None:
    obj = _make_json_repo_info()
    with patch.object(obj, "load_repo_data"):
        obj._view_json()
    data = json.loads(capsys.readouterr().out)
    defaults = [item for item in data if item["default"]]
    assert len(defaults) == 1
    assert defaults[0]["name"] == "sbo"


def test_view_json_package_count_from_repo_data(capsys) -> None:
    obj = _make_json_repo_info()
    with patch.object(obj, "load_repo_data"):
        obj._view_json()
    data = json.loads(capsys.readouterr().out)
    by_name = {item["name"]: item for item in data}
    assert by_name["sbo"]["packages"] == 5
    assert by_name["alien"]["packages"] == 0  # disabled → 0


def test_view_json_single_repository_filter(capsys) -> None:
    obj = _make_json_repo_info(option_repository="sbo")
    with patch.object(obj, "load_repo_data"):
        obj._view_json()
    data = json.loads(capsys.readouterr().out)
    assert len(data) == 1
    assert data[0]["name"] == "sbo"
