#!/usr/bin/env python3
# Copyright: 2026, Maximiliano Curia <maxy@debian.org>
#
# License: ISC
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#  .
#  THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
#  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#  AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
#  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
#  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
#  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
#  PERFORMANCE OF THIS SOFTWARE.

"""Golden-output regression for the full decopy pipeline.

Runs decopy end to end over the small in-repo fixture trees and asserts the
rendered debian/copyright matches a committed golden file byte for byte. This
is the safety net for performance refactors (e.g. a license-detection
prefilter): the output must not change, or the diff must be explained and the
golden regenerated deliberately.

Regenerate the golden files after an intended change with:

    DECOPY_UPDATE_GOLDEN=1 python3 -m pytest tests/golden_output_test.py
"""

from __future__ import annotations

import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path

_REPO_ROOT = Path(__file__).resolve().parent.parent
_TESTDATA = Path(__file__).parent / "testdata"
_GOLDEN_DIR = _TESTDATA / "golden"

# Fixture trees rendered as goldens. All are pure text, so detection does not
# depend on optional external tools (exiftool, *cat) and the output is portable.
_CORPORA = ("one", "two", "three")

# Pinned, explicit options so the golden does not silently track default flips.
_OPTIONS = ["--no-progress", "--no-expand-archives"]


def render_corpus(root: Path, out_path: Path, extra: list[str] | None = None) -> str:
    """Render `root` to `out_path` via the real CLI in a fresh subprocess.

    A subprocess is deliberate: decopy keeps a process-global License registry
    (License.get), so an in-process run would inherit license texts loaded by
    other tests. A fresh interpreter reproduces exactly what the `decopy` CLI
    emits for this tree, independent of test execution order.
    """
    args = [
        sys.executable,
        "-c",
        "from decopy.decopy import main; raise SystemExit(main())",
        *_OPTIONS,
        *(extra or []),
        "--root",
        str(root),
        "-o",
        str(out_path),
    ]
    env = {**os.environ, "PYTHONPATH": str(_REPO_ROOT)}
    subprocess.run(args, check=True, capture_output=True, env=env)  # noqa: S603
    return out_path.read_text(encoding="utf-8")


class TestGoldenOutput(unittest.TestCase):
    """The rendered copyright for each fixture tree matches its golden."""

    def _render(self, corpus: str, extra: list[str] | None = None) -> str:
        with tempfile.TemporaryDirectory() as tmp:
            return render_corpus(_TESTDATA / corpus, Path(tmp) / "copyright", extra)

    def test_matches_golden(self):
        update = os.environ.get("DECOPY_UPDATE_GOLDEN") == "1"
        if update:
            _GOLDEN_DIR.mkdir(parents=True, exist_ok=True)
        for corpus in _CORPORA:
            with self.subTest(corpus=corpus):
                got = self._render(corpus)
                golden = _GOLDEN_DIR / f"{corpus}.copyright"
                if update:
                    golden.write_text(got, encoding="utf-8")
                    continue
                self.assertTrue(
                    golden.exists(),
                    f"Missing golden {golden}; regenerate with DECOPY_UPDATE_GOLDEN=1",
                )
                self.assertEqual(
                    got,
                    golden.read_text(encoding="utf-8"),
                    f"Output for {corpus} drifted from {golden}",
                )

    def test_output_is_deterministic(self):
        # Locks the property the perf work relies on: the threaded pipeline's
        # output does not depend on task completion order.
        for corpus in _CORPORA:
            with self.subTest(corpus=corpus):
                self.assertEqual(self._render(corpus), self._render(corpus))


if __name__ == "__main__":
    unittest.main()
