"""Shared fail-closed package/target contract; no database connection."""
import hashlib
import re

DESTINATIONS = {
    'front1-20261024': {'app': 'ZHCT_APP', 'probe': 'ZHCT_PROBE', 'tablespace': 'ZHCT_TBS'},
    'front2-20261024': {'app': 'ZHCT2_APP', 'probe': 'ZHCT2_PROBE', 'tablespace': 'ZHCT2_TBS'},
}
SOURCES = {'front1-20261024': '434f2ea099e8fa8e397cbe8adae6a5c4a73542fd25c9a94e170316c7d123149f',
           'front2-20261024': '0b04a89f8a54e0aa8749eb398eb95abbf1c8edc6b4c74e8a5b1cffcb48fbe3a6'}
CUSTOMER_BUILD = '--03134284488-20260902-347071-20200 Pack30'
REHEARSAL_BUILD = '--03134284488-20260212-314192-20200'


def destination(manifest):
    wanted = DESTINATIONS.get(manifest.get('dataset'))
    if not wanted or manifest.get('destination') != wanted:
        raise ValueError('Unknown front dataset/destination pair')
    if manifest.get('source_sha256') != SOURCES[manifest['dataset']]:
        raise ValueError('Dataset source backup SHA mismatch')
    return wanted


def validate_target(cfg, manifest, role='app'):
    dest = destination(manifest)
    if cfg.get('dataset') != manifest['dataset'] or role not in ('app', 'probe'):
        raise ValueError('Dataset/role mismatch')
    for field, wanted in [('expectedCompatibleMode', 4), ('expectedCaseSensitive', 0),
                          ('expectedUnicode', 1), ('expectedPageBytes', 32768)]:
        if type(cfg.get(field)) is not int or cfg[field] != wanted:
            raise ValueError('Explicit mode/case/Unicode/page contract required')
    if not re.fullmatch(r'[A-Za-z0-9_]+', cfg.get('password', '')):
        raise ValueError('Unsafe temporary password format')
    if cfg.get('environment') == 'customer':
        valid = (cfg.get('expectedTargetUser') == dest['app'] and cfg.get('expectedProbeUser') == dest['probe']
                 and cfg.get('expectedTablespace') == dest['tablespace'] and cfg.get('expectedInstance') == 'DMSERVER'
                 and str(cfg.get('port')) == '5236' and cfg.get('expectedBuild') == CUSTOMER_BUILD
                 and cfg.get('host') in ('127.0.0.1', '10.10.235.2'))
    elif cfg.get('environment') == 'rehearsal':
        suffixes = ('1', '3') if manifest['dataset'] == 'front1-20261024' else ('2', '4')
        valid = (any(cfg.get('expectedTargetUser') == 'FRONT24_APP' + n and
                     cfg.get('expectedProbeUser') == 'FRONT24_PROBE' + n for n in suffixes)
                 and cfg.get('expectedTablespace') == 'MAIN' and cfg.get('expectedInstance') == 'FS1R20261021'
                 and str(cfg.get('port')) == '15236' and cfg.get('expectedBuild') == REHEARSAL_BUILD
                 and cfg.get('host') == '127.0.0.1')
    else:
        valid = False
    if not valid:
        raise ValueError('Target differs from approved environment/dataset destination')
    if (cfg.get('user') != cfg.get('expectedProbeUser' if role == 'probe' else 'expectedTargetUser')
            or cfg.get('isolationProbeTable') != cfg['expectedProbeUser'] + '.Z_CAP_VALUES'):
        raise ValueError('Business/probe or isolation binding mismatch')


def fingerprint(root, manifest):
    return {'manifest_sha256': hashlib.sha256((root / 'manifest.json').read_bytes()).hexdigest(),
            'source_sha256': manifest['source_sha256'],
            'schema_sha256': manifest['files']['sql/01_schema.sql']['sha256'],
            'column_mapping_sha256': manifest['files']['column-mapping.json']['sha256'],
            'statement_mapping_sha256': manifest['files']['statement-mapping.json']['sha256'],
            'dataset': manifest['dataset']}


def validate_probe_binding(cfg, probe):
    return (probe.get('environment') == cfg['environment'] and probe.get('dataset') == cfg['dataset']
            and probe.get('host') == cfg['host'] and probe.get('probe_user') == cfg['expectedProbeUser']
            and probe.get('target_user') == cfg['expectedTargetUser']
            and probe.get('isolation_probe_table') == cfg['isolationProbeTable']
            and probe.get('isolation_probe_exists') is True)


def verify_files(root, manifest):
    """Verify all frozen payload members, reject path traversal and symlinks."""
    destination(manifest)
    required = {'sql/01_schema.sql', 'sql/03_indexes_constraints.sql', 'column-mapping.json',
                'index-mapping.json', 'identity-source-profile.json', 'statement-mapping.json',
                'target_contract.cjs', 'target_contract.py'}
    if (manifest.get('target_tables') != 284 or not required <= set(manifest['files'])
            or len(manifest.get('tables', [])) != 284):
        raise ValueError('Incomplete frozen front payload contract')
    names = set()
    rows = 0
    for table in manifest['tables']:
        name = table['table']
        if (not re.fullmatch(r'[a-z0-9_]+', name) or name in names
                or type(table['rows']) is not int or table['rows'] < 0):
            raise ValueError('Invalid source table contract')
        names.add(name)
        rows += table['rows']
        if any(f not in manifest['files'] for f in table['data_files']):
            raise ValueError('Unhashed data file')
    if rows != manifest['total_rows']:
        raise ValueError('Source row total mismatch')
    for name, expected in manifest['files'].items():
        parts = name.split('/')
        if not name or not re.fullmatch(r'[A-Za-z0-9_./-]+', name) or name.startswith('/') or any(x in ('', '.', '..') for x in parts):
            raise ValueError('Unsafe payload path')
        path = root
        for part in parts:
            path = path / part
            if path.is_symlink():
                raise ValueError('Payload symlink forbidden')
        if not path.is_file() or path.stat().st_size != expected['bytes']:
            raise ValueError('Payload file size mismatch')
        h = hashlib.sha256()
        with path.open('rb') as f:
            for chunk in iter(lambda: f.read(1024 * 1024), b''):
                h.update(chunk)
        if h.hexdigest() != expected['sha256']:
            raise ValueError('Payload file hash mismatch')
