#!/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.

"""Coverage harness over every SPDX licence in tests/testdata/licenses/.

``license-coverage.tsv`` is the source of truth. Each row is
``spdx_id <TAB> expected <TAB> status <TAB> fields``:

* ``expected`` - the DEP-5 name find_licenses() must produce (empty = a known
  coverage gap: a licence decopy does not yet recognise);
* ``status`` - annotations (ok / undetected / wrong:<note> / ...);
* ``fields`` - comma-separated SPDX text fields to validate against (default
  ``licenseText``). Some ids share an identical ``licenseText`` across their
  ``-only`` / ``-or-later`` variants but differ in ``standardLicenseHeader``;
  those rows validate against ``standardLicenseHeader`` instead.

The test locks find_licenses() output for all 729 files, so neither a matcher
change nor refreshed SPDX data can shift coverage silently: a file with no row
fails, a row with no file fails, and any changed result fails.

Regenerate with ``scripts/audit-license-coverage.py``.
"""

import json
import unittest
from pathlib import Path

from decopy import licenses

LICENSES_DIR = Path("tests/testdata/licenses")
COVERAGE_TSV = Path("tests/testdata/license-coverage.tsv")
DEFAULT_FIELDS = ("licenseText",)
_EXPECTED_COL = 1
_FIELDS_COL = 3


def _load_table() -> dict[str, tuple[str, tuple[str, ...]]]:
    table: dict[str, tuple[str, tuple[str, ...]]] = {}
    for line in COVERAGE_TSV.read_text().splitlines():
        if line.startswith("#") or not line.strip():
            continue
        cols = line.split("\t")
        spdx_id = cols[0]
        expected = cols[_EXPECTED_COL] if len(cols) > _EXPECTED_COL else ""
        raw_fields = cols[_FIELDS_COL] if len(cols) > _FIELDS_COL else ""
        fields = tuple(f for f in raw_fields.split(",") if f) or DEFAULT_FIELDS
        table[spdx_id] = (expected, fields)
    return table


def _detect(text: str) -> str:
    return " or ".join(sorted(licenses.find_licenses(licenses.clean_comments(text))))


class TestLicenseCoverage(unittest.TestCase):
    table: dict[str, tuple[str, tuple[str, ...]]]
    files: list[Path]

    @classmethod
    def setUpClass(cls):
        cls.table = _load_table()
        cls.files = sorted(LICENSES_DIR.glob("*.json"))

    def test_every_licence_matches_table(self):
        for path in self.files:
            spdx_id = path.stem
            with self.subTest(licence=spdx_id):
                self.assertIn(
                    spdx_id,
                    self.table,
                    msg="missing from license-coverage.tsv; "
                    "run scripts/audit-license-coverage.py",
                )
                expected, fields = self.table[spdx_id]
                data = json.loads(path.read_text())
                for field in fields:
                    text = data.get(field, "")
                    if not text:
                        continue
                    self.assertEqual(_detect(text), expected, msg=f"field={field}")

    def test_no_stale_table_rows(self):
        present = {path.stem for path in self.files}
        stale = sorted(set(self.table) - present)
        self.assertEqual(stale, [], msg="rows with no licence file")


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