#!/usr/bin/python3
"""Tests for sbo_create.template_loader.TemplateLoader."""

import pytest

from sbo_create.models import AppContext, set_arm_labels
from sbo_create.template_loader import TemplateLoader

TEMPLATES = ['autotools', 'cmake', 'perl', 'python', 'rubygem', 'haskell', 'meson']


# ---------------------------------------------------------------------------
# slackbuild()
# ---------------------------------------------------------------------------

@pytest.mark.parametrize('tpl', TEMPLATES)
def test_slackbuild_contains_prgnam(tpl: str, ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slackbuild(tpl)
    assert ctx.prg_name in content


@pytest.mark.parametrize('tpl', TEMPLATES)
def test_slackbuild_contains_version(tpl: str, ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slackbuild(tpl)
    assert ctx.info.version in content


@pytest.mark.parametrize('tpl', TEMPLATES)
def test_slackbuild_contains_year(tpl: str, ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slackbuild(tpl)
    assert str(ctx.year) in content


@pytest.mark.parametrize('tpl', TEMPLATES)
def test_slackbuild_contains_maintainer(tpl: str, ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slackbuild(tpl)
    assert ctx.maintainer.name in content


@pytest.mark.parametrize('tpl', TEMPLATES)
def test_slackbuild_contains_location(tpl: str, ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slackbuild(tpl)
    assert ctx.maintainer.where_you_live in content


@pytest.mark.parametrize('tpl', TEMPLATES)
def test_slackbuild_no_leftover_appname(tpl: str, ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slackbuild(tpl)
    assert 'appname' not in content


@pytest.mark.parametrize('tpl', TEMPLATES)
def test_slackbuild_prgnam_line(tpl: str, ctx: AppContext) -> None:
    # perl/rubygem/haskell use a prefix: PRGNAM=perl-myapp
    # autotools/cmake/python/meson use plain: PRGNAM=myapp
    content = TemplateLoader(ctx).slackbuild(tpl)
    prgnam_lines = [ln for ln in content.splitlines() if ln.startswith('PRGNAM=')]
    assert prgnam_lines, "No PRGNAM= line found"
    assert ctx.prg_name in prgnam_lines[0]


def test_slackbuild_missing_template_raises(ctx: AppContext) -> None:
    with pytest.raises(FileNotFoundError):
        TemplateLoader(ctx).slackbuild('nonexistent')


def test_slackbuild_version_in_version_line(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slackbuild('autotools')
    assert f'${{VERSION:-{ctx.info.version}}}' in content


@pytest.mark.parametrize('tpl', TEMPLATES)
def test_slackbuild_strip_comments_keeps_license(tpl: str, ctx: AppContext) -> None:
    """With strip_comments=True, the license header is preserved."""
    ctx.strip_comments = True
    content = TemplateLoader(ctx).slackbuild(tpl)
    assert 'ADVISED OF THE POSSIBILITY' in content


@pytest.mark.parametrize('tpl', TEMPLATES)
def test_slackbuild_strip_comments_removes_inline_comments(tpl: str, ctx: AppContext) -> None:
    """With strip_comments=True, inline comment lines in the body are removed."""
    ctx.strip_comments = False
    full = TemplateLoader(ctx).slackbuild(tpl)
    ctx.strip_comments = True
    stripped = TemplateLoader(ctx).slackbuild(tpl)
    # The stripped version should have fewer lines
    assert len(stripped.splitlines()) < len(full.splitlines())


@pytest.mark.parametrize('tpl', TEMPLATES)
def test_slackbuild_strip_comments_keeps_shebang(tpl: str, ctx: AppContext) -> None:
    """With strip_comments=True, the shebang line is preserved."""
    ctx.strip_comments = True
    content = TemplateLoader(ctx).slackbuild(tpl)
    assert content.startswith('#!/bin/bash')


def test_strip_body_comments_no_consecutive_blank_lines(ctx: AppContext) -> None:
    """strip_body_comments collapses consecutive blank lines into one."""
    content = (
        '#!/bin/bash\n'
        '#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n'
        '\n'
        '# inline comment\n'
        '\n'
        '\n'
        'PRGNAM=test\n'
    )
    result = TemplateLoader._strip_body_comments(content)
    assert '\n\n\n' not in result


def test_strip_body_comments_removes_inline_comments(ctx: AppContext) -> None:
    """Inline comments on code lines are stripped."""
    content = (
        '#!/bin/bash\n'
        '#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n'
        'PRGNAM=myapp    # replace with name of program\n'
        'TAG=${TAG:-_SBo}    # the "_SBo" is required\n'
        'TMP=${TMP:-/tmp/SBo}    # For consistency\n'
    )
    result = TemplateLoader._strip_body_comments(content)
    assert '# replace with name of program' not in result
    assert '# the "_SBo" is required' not in result
    assert '# For consistency' not in result
    assert 'PRGNAM=myapp' in result
    assert '${TAG:-_SBo}' in result


def test_strip_body_comments_preserves_shell_expansion(ctx: AppContext) -> None:
    """Shell parameter expansions with # (e.g. ${var##pat}) are not touched."""
    content = (
        '#!/bin/bash\n'
        '#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n'
        'NAME=${ARCHIVE##*/}\n'
    )
    result = TemplateLoader._strip_body_comments(content)
    assert '${ARCHIVE##*/}' in result


# ---------------------------------------------------------------------------
# info()
# ---------------------------------------------------------------------------

def test_info_contains_prgnam(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).info()
    assert f'PRGNAM="{ctx.prg_name}"' in content


def test_info_contains_version(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).info()
    assert f'VERSION="{ctx.info.version}"' in content


def test_info_contains_maintainer(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).info()
    assert f'MAINTAINER="{ctx.maintainer.name}"' in content


def test_info_contains_email(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).info()
    assert f'EMAIL="{ctx.maintainer.email}"' in content


def test_info_homepage_empty(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).info()
    assert 'HOMEPAGE=""' in content


def test_info_download_empty(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).info()
    assert 'DOWNLOAD=""' in content


def test_info_requires_empty(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).info()
    assert 'REQUIRES=""' in content


def test_info_x86_labels_by_default(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).info()
    assert 'DOWNLOAD_x86_64' in content
    assert 'ARM64' not in content


def test_info_arm_labels_when_arm_support(ctx: AppContext) -> None:
    set_arm_labels(ctx.info)
    content = TemplateLoader(ctx).info()
    assert 'DOWNLOAD_ARM64' in content
    assert 'DOWNLOAD_x86_64' not in content


# ---------------------------------------------------------------------------
# slack_desc()
# ---------------------------------------------------------------------------

def test_slack_desc_contains_prgnam(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slack_desc()
    assert ctx.prg_name in content


def test_slack_desc_no_leftover_appname(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slack_desc()
    assert 'appname' not in content


def test_slack_desc_handy_ruler_aligned(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slack_desc()
    ruler_line = [ln for ln in content.splitlines() if '|-----handy-ruler' in ln][0]
    expected_indent = ' ' * len(ctx.prg_name)
    assert ruler_line.startswith(expected_indent + '|')


def test_slack_desc_prgnam_lines(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).slack_desc()
    desc_lines = [ln for ln in content.splitlines() if ln.startswith(f'{ctx.prg_name}:')]
    assert len(desc_lines) == 11


# ---------------------------------------------------------------------------
# doinst() / douninst()
# ---------------------------------------------------------------------------

def test_doinst_contains_banner(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).doinst()
    assert 'COPY WHAT YOU WANT' in content


def test_doinst_contains_template_content(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).doinst()
    assert 'config()' in content


def test_doinst_has_ansi_color(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).doinst()
    assert '\x1b[' in content


def test_douninst_contains_banner(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).douninst()
    assert 'PRESS' in content and 'TO EXIT' in content


def test_douninst_has_ansi_color(ctx: AppContext) -> None:
    content = TemplateLoader(ctx).douninst()
    assert '\x1b[' in content
