#!/usr/bin/env python3
"""Strict same-fixture response comparison; preserve JSON types and array order."""
import importlib.util
import json
from pathlib import Path
import re
import sys

HERE = Path(__file__).resolve().parent
CONTROL = next(p for p in HERE.parents if p.name == 'zhctprompt')
spec = importlib.util.spec_from_file_location('strict', CONTROL / 'work/2026-09-28-smart-canteen-dm8-poc/http-harness/strict_response_compare.py')
strict = importlib.util.module_from_spec(spec)
spec.loader.exec_module(strict)


def normalized(result):
    out = []
    invalid = []
    own_id = result['owned_ids']['limit']
    marker = result['marker']
    def visit(node, path='$'):
        if isinstance(node, list):
            return [visit(v, f'{path}[{i}]') for i, v in enumerate(node)]
        if not isinstance(node, dict):
            return node
        mapped = {}
        for key, value in node.items():
            at = path + '[' + json.dumps(key) + ']'
            if key == 'id':
                if type(value) is not int or value != own_id:
                    invalid.append((at, 'invalid own rule id', value))
                else:
                    value = 1  # Validated DB-owned generated ID; keep number type.
            elif key in ('limit_name', 'create_by', 'update_by'):
                if type(value) is not str or value != marker:
                    invalid.append((at, 'invalid own marker', value))
                else:
                    value = 'OWN_RULE'
            elif key == 'department_uuids':
                if value != [marker + 'D']:
                    invalid.append((at, 'invalid own department', value))
                else:
                    value = ['OWN_DEPARTMENT']
            elif key in ('create_time', 'update_time'):
                # Do NOT mask fractional-second format differences or stale timestamps.
                if type(value) is not str or not re.fullmatch(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}', value):
                    invalid.append((at, 'expected original seconds timestamp', value))
                else:
                    value = 'VALID_SECONDS_TIMESTAMP'
            mapped[key] = visit(value, at)
        return mapped
    for label, sample in result['samples'].items():
        out.append({'case': label, 'http': sample['http'], 'action': sample['action'],
                    'body': visit(sample['body'], label)})
    # Independently assert lifecycle before mapping only actual wall-clock values.
    create = result['samples']['create']['body']['data']
    updated = result['samples']['update']['body']['data']
    if not updated['update_time'] > create['update_time']:
        invalid.append(('update.update_time', 'must advance after real edit', updated['update_time']))
    if updated['create_time'] != create['create_time']:
        invalid.append(('update.create_time', 'creation time changed', updated['create_time']))
    repeated = result['samples']['repeat_update']['body']['data']
    if repeated['update_time'] != updated['update_time']:
        invalid.append(('repeat_update.update_time', 'identical edit changed timestamp', repeated['update_time']))
    if 'db_states' in result:
        for label, state in result['db_states'].items():
            rows = state['consume_limit']
            if len(rows) != 1:
                invalid.append((label, 'expected owned main row', len(rows)))
                continue
            db_row = rows[0]
            if label in result['samples'] and isinstance(result['samples'][label]['body']['data'], dict):
                api_row = result['samples'][label]['body']['data']
                if 'id' in api_row:
                    for key in db_row:
                        if key not in api_row:
                            invalid.append((label, 'API omitted database field', key))
                            continue
                        value = db_row[key]
                        # Driver raw PDO types vary; SQL INTEGER values must retain exact value.
                        if key in ('id','scope_limit','times_limit','monthly_limit','daily_limit','meal_limit','status','store_id','is_delete'):
                            if not re.fullmatch(r'-?\d+', str(value)) or type(api_row[key]) is not int or int(value) != api_row[key]:
                                invalid.append((label+'.'+key, 'SQL integer/API value mismatch', value))
                        elif key in ('create_time','update_time'):
                            # Explicit second-precision SQL writes may render trailing zero microseconds.
                            if not re.fullmatch(re.escape(api_row[key])+r'(?:\.0+)?', str(value)):
                                invalid.append((label+'.'+key, 'SQL timestamp/API mismatch', value))
                        elif value != api_row[key]:
                            invalid.append((label+'.'+key, 'SQL text/API mismatch', value))
    return out, invalid


def main():
    prefix = 'red-' if '--red' in sys.argv else ''
    b, c = [strict.load_json(HERE / f'{prefix}result-{lane}.json') for lane in 'BC']
    if not b['samples'] or b['samples'].keys() != c['samples'].keys() or b['source'] != c['source']:
        raise ValueError('Empty batch, different scenarios, or source version drift')
    left, bad_b = normalized(b)
    right, bad_c = normalized(c)
    diff = strict.compare(left, right)
    answer = {'status': 'PASS' if not diff and not bad_b and not bad_c else 'FAIL',
              'pairs': len(left), 'response_differences': len(diff),
              'B_contract_errors': bad_b, 'C_contract_errors': bad_c,
              'differences': diff}
    print(json.dumps(answer, ensure_ascii=False, default=str, indent=2))
    return 0 if answer['status'] == 'PASS' else 1


if __name__ == '__main__':
    raise SystemExit(main())
