"""Local PS256 and fictitious rotation readiness. Never contacts NetSuite."""
from pathlib import Path
from dataclasses import dataclass, replace
import base64
import json
import os
import subprocess
import tempfile
import time


def openssl(*args, required=True):
    result = subprocess.run(['openssl', *map(str, args)], capture_output=True)
    if required and result.returncode:
        raise RuntimeError('Local OpenSSL command failed; no remote action was attempted.')
    return result


def encoded(value):
    raw = json.dumps(value, separators=(',', ':'), sort_keys=True).encode()
    return base64.urlsafe_b64encode(raw).rstrip(b'=')


def assertion_input(header, claims):
    return encoded(header) + b'.' + encoded(claims)


def metadata_valid(header, claims, kid, audience, issuer, now):
    return (
        header == {'typ': 'JWT', 'alg': 'PS256', 'kid': kid}
        and claims['aud'] == audience
        and claims['iss'] == issuer
        and claims['scope'] == 'rest_webservices'
        and isinstance(claims['iat'], int)
        and isinstance(claims['exp'], int)
        and claims['iat'] <= now < claims['exp']
        and 0 < claims['exp'] - claims['iat'] < 3600
    )


@dataclass(frozen=True)
class Evidence:
    mapping_confirmed: bool
    signature_matches_public_key: bool
    fresh_token_observed: bool
    intended_read_observed: bool
    workers_migrated: bool


def retirement_decision(evidence):
    for field_name in Evidence.__dataclass_fields__:
        if not getattr(evidence, field_name):
            return 'WAIT:' + field_name
    return 'LOCAL_RETIREMENT_CHECKLIST_READY_NOT_REMOTE_REVOCATION'


def main():
    checks = 0

    def check(condition):
        nonlocal checks
        checks += 1
        if not condition:
            raise AssertionError('Local check failed: ' + str(checks))

    now = int(time.time())
    kid = 'mapping-B-fictitious'
    audience = 'https://account-fictif.suitetalk.api.netsuite.com/services/rest/auth/oauth2/v1/token'
    issuer = 'integration-fictitious'
    header = {'typ': 'JWT', 'alg': 'PS256', 'kid': kid}
    claims = {'iss': issuer, 'scope': 'rest_webservices', 'aud': audience, 'iat': now, 'exp': now + 300}
    pss = ['-sigopt', 'rsa_padding_mode:pss', '-sigopt', 'rsa_pss_saltlen:32', '-sigopt', 'rsa_mgf1_md:sha256']
    with tempfile.TemporaryDirectory(prefix='netsuite-local-proof-') as directory:
        root = Path(directory)
        os.chmod(root, 0o700)
        for label in ('A', 'B'):
            key = root / (label + '.key')
            cert = root / (label + '.crt')
            public = root / (label + '.pub')
            openssl('genpkey', '-algorithm', 'RSA', '-pkeyopt', 'rsa_keygen_bits:3072', '-out', key)
            key.chmod(0o600)
            check(key.stat().st_mode & 0o777 == 0o600)
            openssl('req', '-new', '-x509', '-key', key, '-out', cert,
                    '-subj', '/CN=local-fixture-' + label, '-days', '1')
            public.write_bytes(openssl('x509', '-in', cert, '-pubkey', '-noout').stdout)
        message = root / 'message.bin'
        signature = root / 'signature.bin'
        message.write_bytes(assertion_input(header, claims))
        openssl('dgst', '-sha256', '-sign', root / 'B.key', '-out', signature, *pss, message)

        def verifies(public):
            return openssl('dgst', '-sha256', '-verify', public, '-signature', signature,
                           *pss, message, required=False).returncode == 0

        check(verifies(root / 'B.pub'))
        check(not verifies(root / 'A.pub'))
        message.write_bytes(assertion_input(header, dict(claims, aud='https://changed.invalid/token')))
        check(not verifies(root / 'B.pub'))
        print('PS256: expected public key accepted; other key and modified payload refused locally.')
    # Temporary keys, certificates, signing input and signature are now removed.
    check(not root.exists())
    check(metadata_valid(header, claims, kid, audience, issuer, now))
    check(not metadata_valid(dict(header, kid='mapping-A-fictitious'), claims, kid, audience, issuer, now))
    check(not metadata_valid(header, dict(claims, aud='https://account-fictif.restlets.api.netsuite.com/certificates'), kid, audience, issuer, now))
    check(not metadata_valid(header, dict(claims, iss='other-integration'), kid, audience, issuer, now))
    check(not metadata_valid(header, dict(claims, scope='restlets'), kid, audience, issuer, now))
    check(metadata_valid(header, dict(claims, exp=now + 3599), kid, audience, issuer, now))
    check(not metadata_valid(header, dict(claims, exp=now + 3600), kid, audience, issuer, now))
    check(not metadata_valid(header, dict(claims, exp=now), kid, audience, issuer, now))
    check(not metadata_valid(header, dict(claims, iat=now + 1), kid, audience, issuer, now))
    check(not metadata_valid(dict(header, alg='RS256'), claims, kid, audience, issuer, now))
    print('Local metadata guards: mapping ID, audience, integration, selected scope and time boundaries checked.')
    ready = Evidence(True, True, True, True, True)
    for field_name in Evidence.__dataclass_fields__:
        check(retirement_decision(replace(ready, **{field_name: False})) == 'WAIT:' + field_name)
    check(retirement_decision(ready) == 'LOCAL_RETIREMENT_CHECKLIST_READY_NOT_REMOTE_REVOCATION')
    # Count only already-qualified active mappings; no native expiry inference.
    check(4 + 1 <= 5)
    check(not 5 + 1 <= 5)
    check(retirement_decision(ready) != 'CERTIFICATE_REVOKED')
    print(str(checks) + ' checks passed. New local fixture keys only; no token printed, NetSuite call or certificate revocation.')


if __name__ == '__main__':
    main()
