#!/usr/bin/env python3
"""Contracts for public docs, built-site validation, and benchmark reporting."""
import copy
import importlib.util
import json
from pathlib import Path
import re
import tempfile
import unittest

ROOT = Path(__file__).resolve().parents[1]


def module(name):
    spec = importlib.util.spec_from_file_location(name, ROOT / 'scripts' / f'{name}.py')
    result = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(result)
    return result


site = module('check_site')
report = module('benchmark_report')


class PagesContracts(unittest.TestCase):
    def test_navigation_resolves_and_metadata_is_distinct(self):
        paths, titles, descriptions = set(), set(), set()
        for document in [ROOT / 'index.html', *(ROOT / 'docs').glob('*.md')]:
            text = document.read_text()
            self.assertTrue(text.startswith('---\n'), document)
            frontmatter = text.split('---', 2)[1]
            fields = dict(re.findall(r'^([a-z_]+):\s*(.+)$', frontmatter, re.M))
            for key, seen in [('title', titles), ('description', descriptions), ('permalink', paths)]:
                value = fields[key].strip('"\'')
                self.assertNotIn(value, seen, document)
                seen.add(value)
        navigation = (ROOT / '_data/navigation.yml').read_text()
        for url in re.findall(r'^  url: (.+)$', navigation, re.M):
            self.assertIn(url, paths)
        self.assertEqual(len(paths), len(re.findall(r'^  url: (.+)$', navigation, re.M)) + 1)

    def test_homepage_starts_with_demo_and_benchmark(self):
        homepage = (ROOT / 'index.html').read_text()
        hero = homepage.split('<section id="benchmarks"')[0]
        self.assertIn('QUICKSTART.html', hero)
        self.assertIn('BENCHMARKS.html', hero)
        self.assertIn('unnest(local_cache.mget', hero)
        self.assertNotIn('curl -fsSL', hero)
        self.assertNotIn('1.3.0', homepage)

    def test_local_markdown_links_resolve(self):
        failures = []
        for document in [ROOT / 'README.md', ROOT / 'CONTRIBUTING.md', *(ROOT / 'docs').glob('*.md')]:
            for target in re.findall(r'\[[^]]*\]\(([^)]+)\)', document.read_text()):
                if target.startswith(('http://', 'https://', '#', 'mailto:')):
                    continue
                path = target.split('#', 1)[0]
                if path and not (document.parent / path).resolve().exists():
                    failures.append(f'{document.name} -> {target}')
        self.assertEqual(failures, [])

    def test_sitemap_and_workflow_cover_the_built_pages(self):
        self.assertIn('site.pages', (ROOT / 'sitemap.xml').read_text())
        workflow = (ROOT / '.github/workflows/pages.yml').read_text()
        self.assertIn('python3 scripts/check_site.py _site', workflow)
        self.assertIn('needs: validate', workflow)
        self.assertIn('site_smoke.py _site', workflow)
        self.assertNotIn('docs/2.0-adoption', workflow)
        self.assertNotIn('site_manifest', workflow)
        layout = (ROOT / '_layouts/default.html').read_text()
        self.assertIn('rel="canonical"', layout)
        self.assertIn('application/ld+json', layout)
        self.assertIn('google_site_verification', layout)

    def test_benchmark_defines_metrics_and_scope(self):
        document = (ROOT / 'docs/BENCHMARKS.md').read_text()
        self.assertIn('requests/s', document)
        self.assertIn('benchmark.json', document)
        self.assertNotIn('1.3.0', document)
        self.assertIn('closed-loop', document.lower())
        self.assertIn('coordinated omission', document)


class BuiltSiteChecks(unittest.TestCase):
    def fixture(self, root):
        (root / 'index.html').write_text('''<!doctype html><html><head><title>Demo</title>
<meta name="description" content="A demo"><meta name="robots" content="index,follow">
<meta property="og:title" content="Demo"><meta property="og:description" content="A demo">
<meta name="twitter:title" content="Demo"><meta name="twitter:description" content="A demo">
<meta property="og:image" content="https://profundium.github.io/pg_local_cache/card.png">
<meta name="twitter:image" content="https://profundium.github.io/pg_local_cache/card.png">
<meta property="og:url" content="https://profundium.github.io/pg_local_cache/">
<link rel="canonical" href="https://profundium.github.io/pg_local_cache/">
<script type="application/ld+json">{"@type":"SoftwareSourceCode"}</script>
</head><body><main id="main-content"><h1>Demo</h1><a href="#code">Code</a>
<pre id="code">SELECT 1</pre><button data-copy="code">Copy</button></main></body></html>''')
        (root / 'card.png').write_bytes(b'image fixture')
        (root / 'sitemap.xml').write_text('''<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"><url><loc>https://profundium.github.io/pg_local_cache/</loc></url></urlset>''')

    def test_rejects_noindex_and_wrong_canonical_host(self):
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            self.fixture(root)
            path = root / 'index.html'
            path.write_text(path.read_text().replace('content="index,follow"', 'content="noindex,follow"'))
            self.assertTrue(any('indexing policy' in error for error in site.check(root)))
            self.fixture(root)
            path.write_text(path.read_text().replace(site.BASE, 'https://example.com/'))
            self.assertTrue(any('canonical' in error for error in site.check(root)))

    def test_missing_social_image_and_duplicate_canonical(self):
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            self.fixture(root)
            (root / 'card.png').unlink()
            path = root / 'index.html'
            path.write_text(path.read_text().replace('</head>',
                '<link rel="canonical" href="https://profundium.github.io/pg_local_cache/"></head>'))
            errors = site.check(root)
            self.assertTrue(any('card.png' in error for error in errors))
            self.assertTrue(any('duplicate canonical' in error for error in errors))

    def test_valid_artifact(self):
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            self.fixture(root)
            self.assertEqual(site.check(root), [])

    def test_svg_title_does_not_change_page_title(self):
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            self.fixture(root)
            path = root / 'index.html'
            path.write_text(path.read_text().replace('</main>',
                '<svg role="img" aria-labelledby="diagram-title"><title id="diagram-title">Read path</title></svg></main>'))
            self.assertEqual(site.check(root), [])

    def test_sitemap_entry_cannot_replace_a_crawlable_link(self):
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            self.fixture(root)
            home = root / 'index.html'
            orphan = home.read_text().replace('Demo', 'Guide').replace('A demo', 'A guide')
            orphan = orphan.replace('href="' + site.BASE + '"', 'href="' + site.BASE + 'guide.html"')
            orphan = orphan.replace('content="' + site.BASE + '"', 'content="' + site.BASE + 'guide.html"')
            (root / 'guide.html').write_text(orphan)
            sitemap = root / 'sitemap.xml'
            sitemap.write_text(sitemap.read_text().replace('</urlset>',
                '<url><loc>' + site.BASE + 'guide.html</loc></url></urlset>'))
            self.assertEqual(site.check(root), [site.BASE + 'guide.html: page is not reachable through links from the homepage'])
            home.write_text(home.read_text().replace('</main>', '<a href="guide.html">Guide</a></main>'))
            self.assertEqual(site.check(root), [])

    def test_broken_fragment_and_copy_target(self):
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            self.fixture(root)
            path = root / 'index.html'
            path.write_text(path.read_text().replace('id="code"', 'id="different"'))
            errors = site.check(root)
            self.assertTrue(any('missing fragment' in error for error in errors))
            self.assertTrue(any('copy target' in error for error in errors))

    def test_bad_json_and_missing_page(self):
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            self.fixture(root)
            path = root / 'index.html'
            path.write_text(path.read_text().replace('{"@type":"SoftwareSourceCode"}', '{bad}').replace('href="#code"', 'href="missing.html"'))
            errors = site.check(root)
            self.assertTrue(any('invalid JSON-LD' in error for error in errors))
            self.assertTrue(any('missing local target' in error for error in errors))


class BenchmarkReportChecks(unittest.TestCase):
    def sample(self):
        return {'schema': 1, 'measured_at': 'test fixture',
                'environment': {'postgres_version': '16', 'extension_version': '2.0.1'},
                'extension_ref': 'test', 'harness_ref': 'test', 'results': [
                    {'repeat': 1, 'workload': 'warm', 'mode': 'mget', 'batch': 16,
                     'requests_s': 100, 'requested_read_keys_s': 1600,
                     'read_latency': {'samples': 10, 'p50_ms': 1, 'p95_ms': 2, 'p99_ms': 3},
                     'write_latency': None}]}

    def test_keeps_repetitions_separate(self):
        data = self.sample()
        second = copy.deepcopy(data['results'][0])
        second['repeat'] = 2
        data['results'].append(second)
        text = report.summary(data)
        self.assertIn('| 1 | warm | mget', text)
        self.assertIn('| 2 | warm | mget', text)
        self.assertIn('1,600.000', text)

    def test_rejects_invalid_measurements(self):
        for value in [float('nan'), float('inf'), -1, True, '100']:
            with self.assertRaises(ValueError):
                report.number(value)
        with self.assertRaises(ValueError):
            report.summary({'schema': 1, 'results': []})


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