"""Unit tests for the Commit Linter (Conventional Commits)."""

import sys
import os
import tempfile

import pytest

sys.path.insert(0, os.path.join(
    os.path.dirname(__file__), "..", "engineering", "skills", "changelog-generator", "scripts"
))
from commit_linter import lint, CONVENTIONAL_RE, lines_from_file, CLIError


class TestConventionalCommitRegex:
    """Test the regex pattern against various commit message formats."""

    @pytest.mark.parametrize("msg", [
        "feat: add user authentication",
        "fix: resolve null pointer in parser",
        "docs: update API documentation",
        "refactor: simplify login flow",
        "test: add integration tests for auth",
        "build: upgrade webpack to v5",
        "ci: add GitHub Actions workflow",
        "chore: update dependencies",
        "perf: optimize database queries",
        "security: patch XSS vulnerability",
        "deprecated: mark v1 API as deprecated",
        "remove: drop legacy payment module",
    ])
    def test_valid_types(self, msg):
        assert CONVENTIONAL_RE.match(msg) is not None

    @pytest.mark.parametrize("msg", [
        "feat(auth): add OAuth2 support",
        "fix(parser/html): handle malformed tags",
        "docs(api.v2): update endpoint docs",
    ])
    def test_valid_scopes(self, msg):
        assert CONVENTIONAL_RE.match(msg) is not None

    def test_breaking_change_marker(self):
        assert CONVENTIONAL_RE.match("feat!: redesign API") is not None
        assert CONVENTIONAL_RE.match("feat(api)!: breaking change") is not None

    @pytest.mark.parametrize("msg", [
        "Update readme",
        "Fixed the bug",
        "WIP: something",
        "FEAT: uppercase type",
        "feat:missing space",
        "feat : extra space before colon",
        "",
        "merge: not a valid type",
    ])
    def test_invalid_messages(self, msg):
        assert CONVENTIONAL_RE.match(msg) is None


class TestLint:
    def test_all_valid(self):
        lines = [
            "feat: add login",
            "fix: resolve crash",
            "docs: update README",
        ]
        report = lint(lines)
        assert report.total == 3
        assert report.valid == 3
        assert report.invalid == 0
        assert report.violations == []

    def test_mixed_valid_invalid(self):
        lines = [
            "feat: add login",
            "Updated the readme",
            "fix: resolve crash",
        ]
        report = lint(lines)
        assert report.total == 3
        assert report.valid == 2
        assert report.invalid == 1
        assert "line 2" in report.violations[0]

    def test_all_invalid(self):
        lines = ["bad commit", "another bad one"]
        report = lint(lines)
        assert report.valid == 0
        assert report.invalid == 2

    def test_empty_input(self):
        report = lint([])
        assert report.total == 0
        assert report.valid == 0
        assert report.invalid == 0


class TestLinesFromFile:
    def test_reads_file(self):
        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
            f.write("feat: add feature\nfix: fix bug\n")
            f.flush()
            lines = lines_from_file(f.name)
        os.unlink(f.name)
        assert lines == ["feat: add feature", "fix: fix bug"]

    def test_skips_blank_lines(self):
        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
            f.write("feat: add feature\n\n\nfix: fix bug\n")
            f.flush()
            lines = lines_from_file(f.name)
        os.unlink(f.name)
        assert len(lines) == 2

    def test_nonexistent_file_raises(self):
        with pytest.raises(CLIError, match="Failed reading"):
            lines_from_file("/nonexistent/path.txt")
