"""Offline CLI tests with synthetic data only. Run: python3 test_verify_delivery.py"""

import gzip
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest


SCRIPT = Path(__file__).with_name('verify-delivery.py')
RECORD = {'ray_id': '0000000000000001', 'status': 403,
          'client_ip_redacted': '[REDACTED]', 'provider': 'cloudflare'}


class DeliveryPolicyTests(unittest.TestCase):
    def run_check(self, content, args=(), compressed=False):
        with tempfile.TemporaryDirectory() as folder:
            path = Path(folder) / ('export.ndjson.gz' if compressed else 'export.ndjson')
            data = content.encode('utf-8') if isinstance(content, str) else content
            path.write_bytes(gzip.compress(data) if compressed else data)
            process = subprocess.run([sys.executable, str(SCRIPT), str(path), *args],
                                     capture_output=True, text=True)
        self.assertEqual(process.stderr, '')
        return process.returncode, json.loads(process.stdout), process.stdout

    def test_valid_plain_and_gzip(self):
        for compressed in (False, True):
            with self.subTest(compressed=compressed):
                code, report, _ = self.run_check('\n' + json.dumps(RECORD) + '\n',
                                                 compressed=compressed)
                self.assertEqual(code, 0)
                self.assertTrue(report['policy_valid'])
                self.assertEqual(report['valid_records'], 1)

    def test_multiple_expected_events_and_duplicates(self):
        other = dict(RECORD, ray_id='0000000000000002')
        code, report, _ = self.run_check(
            '\n'.join(map(json.dumps, [RECORD, other, RECORD])),
            ('--expect-ray-id', RECORD['ray_id'], '--expect-ray-id', other['ray_id']))
        self.assertEqual(code, 0)
        self.assertEqual(report['records'], 3)
        self.assertEqual(report['matched_event_count'], 2)

    def test_missing_expected_event_is_not_reported_as_delivered(self):
        code, report, output = self.run_check(
            json.dumps(RECORD), ('--expect-ray-id', 'private-test-event'))
        self.assertEqual(code, 1)
        self.assertEqual(report['missing_event_count'], 1)
        self.assertNotIn('private-test-event', output)

    def test_unexpected_field_and_value_are_not_echoed(self):
        code, report, output = self.run_check(
            json.dumps(dict(RECORD, private_field='private-user-data')))
        self.assertEqual(code, 1)
        self.assertEqual(report['errors'][0]['code'], 'field_set_mismatch')
        self.assertNotIn('private_field', output)
        self.assertNotIn('private-user-data', output)

    def test_required_fields_and_types(self):
        cases = [dict(RECORD, status=value) for value in (True, 403.0, '403', 200)]
        cases += [dict(RECORD, ray_id=value) for value in ('', ' ', None, 1)]
        cases += [dict(RECORD, provider='other'), dict(RECORD, client_ip_redacted='2001:db8::42'),
                  {key: value for key, value in RECORD.items() if key != 'status'}, []]
        for row in cases:
            with self.subTest(record=row):
                code, report, output = self.run_check(json.dumps(row))
                self.assertEqual(code, 1)
                self.assertEqual(report['invalid_records'], 1)
                self.assertNotIn('2001:db8::42', output)

    def test_invalid_records_do_not_satisfy_expected_event(self):
        code, report, _ = self.run_check(json.dumps(dict(RECORD, status=200)),
                                        ('--expect-ray-id', RECORD['ray_id']))
        self.assertEqual(code, 1)
        self.assertEqual(report['matched_event_count'], 0)

    def test_malformed_duplicate_and_nonstandard_json(self):
        for raw in ('{broken', '{"status":403,"status":404}', '{"status":NaN}'):
            with self.subTest(raw=raw):
                code, report, _ = self.run_check(raw)
                self.assertEqual(code, 1)
                self.assertEqual(report['errors'][0]['code'], 'invalid_json')

    def test_empty_input_does_not_pass(self):
        code, report, _ = self.run_check(' \n\n')
        self.assertEqual(code, 1)
        self.assertEqual(report['errors'][0]['code'], 'empty_input')

    def test_errors_are_bounded_and_include_physical_line(self):
        code, report, _ = self.run_check('\n' + '{broken\n' * 25)
        self.assertEqual(code, 1)
        self.assertEqual(report['error_count'], 25)
        self.assertEqual(len(report['errors']), 20)
        self.assertEqual(report['errors'][0]['line'], 2)
        self.assertTrue(report['error_details_truncated'])

    def test_invalid_utf8_and_broken_gzip(self):
        code, report, _ = self.run_check(b'\xff')
        self.assertEqual(code, 2)
        self.assertEqual(report['errors'][0]['code'], 'input_read_failed')
        with tempfile.TemporaryDirectory() as folder:
            path = Path(folder) / 'broken.ndjson.gz'
            payload = gzip.compress(json.dumps(RECORD).encode())
            corrupt = bytearray(payload)
            corrupt[10] = 0x07  # Reserved DEFLATE block type.
            for data in (payload[:-5], b'not a gzip stream', bytes(corrupt)):
                path.write_bytes(data)
                process = subprocess.run([sys.executable, str(SCRIPT), str(path)],
                                         capture_output=True, text=True)
                self.assertEqual(process.returncode, 2)
                self.assertIn('input_read_failed', process.stdout)
                self.assertEqual(process.stderr, '')

    def test_missing_file_is_safe_read_error(self):
        with tempfile.TemporaryDirectory() as folder:
            process = subprocess.run([sys.executable, str(SCRIPT),
                                      str(Path(folder) / 'private-missing-file')],
                                     capture_output=True, text=True)
        self.assertEqual(process.returncode, 2)
        self.assertIn('input_read_failed', process.stdout)
        self.assertNotIn('private-missing-file', process.stdout)
        self.assertEqual(process.stderr, '')


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