#!/usr/bin/python3
"""Tests for sbo_create.handlers.bump.BumpHandler."""

from pathlib import Path
from unittest.mock import patch

import pytest

from sbo_create.handlers.bump import BumpHandler
from sbo_create.handlers.download import DownloadHandler
from sbo_create.handlers.fallback import SboResolver
from sbo_create.handlers.files import FileHandler
from sbo_create.handlers.validate import ValidateHandler
from sbo_create.models import AppContext


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def bump(ctx: AppContext, tmp_path: Path) -> BumpHandler:
    ctx.current_folder = tmp_path
    ctx.config_path = tmp_path / 'config'
    ctx.config_path.mkdir(parents=True, exist_ok=True)
    files = FileHandler(ctx)
    resolver = SboResolver(ctx, files)
    validate_handler = ValidateHandler(ctx, files, resolver)
    download_handler = DownloadHandler(ctx, files)
    return BumpHandler(ctx, files, validate_handler, download_handler)


def write_info(tmp_path: Path, name: str, version: str = '1.0',
               download: str = '') -> Path:
    """Write a minimal .info file and return its path."""
    content = (
        f'PRGNAM="{name}"\n'
        f'VERSION="{version}"\n'
        f'DOWNLOAD="{download}"\n'
        f'MD5SUM="abc123"\n'
        f'DOWNLOAD_x86_64=""\n'
        f'MD5SUM_x86_64=""\n'
        f'REQUIRES=""\n'
    )
    p = tmp_path / f'{name}.info'
    p.write_text(content, encoding='utf-8')
    return p


def write_slackbuild(tmp_path: Path, name: str, version: str = '1.0') -> Path:
    """Write a minimal .SlackBuild file and return its path."""
    content = (
        f'PRGNAM={name}\n'
        f'VERSION=${{VERSION:-{version}}}\n'
    )
    p = tmp_path / f'{name}.SlackBuild'
    p.write_text(content, encoding='utf-8')
    return p


# ---------------------------------------------------------------------------
# _bump_download_urls
# ---------------------------------------------------------------------------

def test_bump_download_urls_replaces_version_in_url(bump: BumpHandler) -> None:
    content = 'DOWNLOAD="https://example.com/app-1.0.tar.gz"\n'
    result, changed = bump._bump_download_urls(content, '1.0', '2.0')
    assert '2.0' in result
    assert changed is True


def test_bump_download_urls_no_version_in_url_warns(bump: BumpHandler,
                                                     capsys: pytest.CaptureFixture) -> None:
    content = 'DOWNLOAD="https://example.com/app.tar.gz"\n'
    _, changed = bump._bump_download_urls(content, '1.0', '2.0')
    captured = capsys.readouterr()
    assert 'WARNING' in captured.out
    assert changed is False


def test_bump_download_urls_no_download_block_unchanged(bump: BumpHandler) -> None:
    content = 'VERSION="1.0"\n'
    result, changed = bump._bump_download_urls(content, '1.0', '2.0')
    assert result == content
    assert changed is False


# ---------------------------------------------------------------------------
# bump_version — no files
# ---------------------------------------------------------------------------

def test_bump_version_no_files_exits_1(bump: BumpHandler,
                                       capsys: pytest.CaptureFixture) -> None:
    with pytest.raises(SystemExit) as exc_info:
        bump.bump_version('2.0')
    assert exc_info.value.code == 1
    assert 'No .info or .SlackBuild' in capsys.readouterr().out


# ---------------------------------------------------------------------------
# bump_version — info file only
# ---------------------------------------------------------------------------

def test_bump_version_updates_version_in_info(bump: BumpHandler, tmp_path: Path,
                                               ctx: AppContext) -> None:
    write_info(tmp_path, ctx.prg_name)
    with pytest.raises(SystemExit):
        bump.bump_version('2.0')
    assert 'VERSION="2.0"' in (tmp_path / f'{ctx.prg_name}.info').read_text()


def test_bump_version_exits_0_on_success(bump: BumpHandler, tmp_path: Path,
                                          ctx: AppContext) -> None:
    write_info(tmp_path, ctx.prg_name)
    with pytest.raises(SystemExit) as exc_info:
        bump.bump_version('2.0')
    assert exc_info.value.code == 0


def test_bump_version_url_changed_prints_note(bump: BumpHandler, tmp_path: Path,
                                               ctx: AppContext,
                                               capsys: pytest.CaptureFixture) -> None:
    write_info(tmp_path, ctx.prg_name,
               download='https://example.com/app-1.0.tar.gz')
    with pytest.raises(SystemExit):
        bump.bump_version('2.0')
    assert 'Note:' in capsys.readouterr().out


# ---------------------------------------------------------------------------
# bump_version — SlackBuild file
# ---------------------------------------------------------------------------

def test_bump_version_updates_slackbuild(bump: BumpHandler, tmp_path: Path,
                                          ctx: AppContext) -> None:
    write_info(tmp_path, ctx.prg_name)
    sb = write_slackbuild(tmp_path, ctx.prg_name)
    with pytest.raises(SystemExit):
        bump.bump_version('2.0')
    assert '${VERSION:-2.0}' in sb.read_text()


def test_bump_version_slackbuild_only(bump: BumpHandler, tmp_path: Path,
                                       ctx: AppContext) -> None:
    sb = write_slackbuild(tmp_path, ctx.prg_name)
    with pytest.raises(SystemExit) as exc_info:
        bump.bump_version('2.0')
    assert exc_info.value.code == 0
    assert '${VERSION:-2.0}' in sb.read_text()


# ---------------------------------------------------------------------------
# bump_version — with_sources
# ---------------------------------------------------------------------------

def test_bump_version_with_sources_exits_0(bump: BumpHandler, tmp_path: Path,
                                           ctx: AppContext) -> None:
    write_info(tmp_path, ctx.prg_name,
               download='https://example.com/app-1.0.tar.gz')
    with patch('builtins.input', return_value='y'), \
         patch.object(bump.download, 'run_downloads', return_value=['newmd5']):
        with pytest.raises(SystemExit) as exc_info:
            bump.bump_version('2.0', with_sources=True)
    assert exc_info.value.code == 0


def test_bump_version_with_sources_user_declines_exits_0(bump: BumpHandler, tmp_path: Path,
                                                          ctx: AppContext) -> None:
    write_info(tmp_path, ctx.prg_name)
    with patch('builtins.input', return_value='n'):
        with pytest.raises(SystemExit) as exc_info:
            bump.bump_version('2.0', with_sources=True)
    assert exc_info.value.code == 0


# ---------------------------------------------------------------------------
# _bump_md5sums
# ---------------------------------------------------------------------------

def test_bump_md5sums_updates_md5sum_field(bump: BumpHandler, tmp_path: Path,
                                            ctx: AppContext) -> None:
    write_info(tmp_path, ctx.prg_name,
               download='https://example.com/app-1.0.tar.gz')
    content = 'MD5SUM="oldmd5"\nDOWNLOAD="https://example.com/app-1.0.tar.gz"\nMD5SUM_x86_64=""\n'
    with patch.object(bump.download, 'run_downloads', return_value=['newmd5abc']):
        result, all_ok = bump._bump_md5sums(content)
    assert 'newmd5abc' in result
    assert all_ok is True
