# Execute architectural clients against the unchanged archived library.
import array
import contextlib
import hashlib
import io
import json
from pathlib import Path
import tempfile
import time
import zipfile
import sys

from hearthwright import Building

LAST = None


def generate_request(request_json, prepared=None, started=None):
    """Execute a client, validate and reload its export, then return preview cells."""
    global LAST
    request = json.loads(request_json)
    seed = int(request['seed'])
    if not -(2**63) <= seed < 2**63:
        raise ValueError('Seed must be a signed 64-bit integer.')
    namespace = {'__name__': '__browser__', 'SEED': seed}
    started = time.perf_counter() if started is None else started
    stdout = io.StringIO()
    with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stdout):
        if prepared is None:
            exec(compile(request['source'], 'client.py', 'exec'), namespace)
        else:
            namespace['building'] = prepared
    building = namespace.get('building')
    if not isinstance(building, Building):
        raise ValueError('Client must produce a Hearthwright Building named building.')
    validation = building.validate().require_valid().to_dict()
    built = time.perf_counter()
    with tempfile.TemporaryDirectory() as temporary:
        path = Path(temporary) / 'building.litematic'
        building.export(path)
        roundtrip = building.verify(path)
        reloaded = Building.load(path).validate().require_valid().to_dict()
        schematic = path.read_bytes()
        sidecar = path.with_suffix('.json').read_bytes()
    checked = time.perf_counter()
    grid = building.grid
    ox, oy, oz = grid.metadata['origin']
    palette = sorted(set(grid.blocks.values()))
    lookup = {state: i for i, state in enumerate(palette)}
    cells = [(x - ox, y - oy, z - oz, lookup[state]) for (x, y, z), state in sorted(grid.blocks.items())]
    metadata = {
        'seed': str(seed),
        'name': grid.metadata['name'],
        'size': grid.metadata['size_xyz'],
        'palette': palette,
        'validation': validation,
        'reloaded_validation': reloaded,
        'roundtrip': roundtrip,
        'record': json.loads(sidecar),
        'record_json': sidecar.decode(),
        'runtime': {
            'python': sys.version,
            'hash_width': sys.hash_info.width
        },
        'signature': grid.metadata['geometry_signature'],
        'structural_signature': grid.metadata['structural_signature'],
        'stdout': stdout.getvalue(),
        'schematic_sha256': hashlib.sha256(schematic).hexdigest(),
        'full_state_sha256': hashlib.sha256(json.dumps([grid.metadata['size_xyz'], palette, cells], separators=(',', ':')).encode()).hexdigest(),
        'timings': {
            'generation_ms': (built - started) * 1000,
            'roundtrip_ms': (checked - built) * 1000
        },
        'exploration': request.get('exploration'),
        'client_source': request.get('client_source'),
        'execution_source': request['source'],
    }
    LAST = schematic, sidecar, request, metadata
    return json.dumps(metadata), array.array('I', (v for c in cells for v in c)).tobytes()


def explore_request(seed_text):
    """Return one validated broader composition with a standalone editable client."""
    from explorer import explore
    started = time.perf_counter()
    building, source, exploration = explore(int(seed_text))
    request = {'seed': seed_text, 'client_source': source, 'exploration': exploration, 'source': source + '\nfrom hearthwright import generate\nbuilding = generate(DESIGN, seed=SEED)\n'}
    return generate_request(json.dumps(request), prepared=building, started=started)


def export_bundle():
    """Bundle original MCIO export, required metadata and the executed client."""
    if LAST is None:
        raise ValueError('Generate a building first.')
    schematic, sidecar, request, metadata = LAST
    output = io.BytesIO()
    with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as archive:
        archive.writestr('building.litematic', schematic)
        archive.writestr('building.json', sidecar)
        archive.writestr('client.py', 'SEED = ' + request['seed'] + '\n' + request['source'] + '\nfrom pathlib import Path\nbuilding.export(Path("building.litematic"))\n')
        archive.writestr('README.txt', 'Install the report source package and its MCIO dependencies.\n'
                         'Run python client.py. Keep building.json beside building.litematic for validation.\n')
    return output.getvalue()


def export_litematic():
    """Return the original MCIO bytes for interoperability checks."""
    if LAST is None:
        raise ValueError('Generate a building first.')
    return LAST[0]
