"""Replay pinned GIMP-harness state boundaries without GIMP or image rendering."""
import hashlib
import importlib.metadata
import json
from pathlib import Path
import platform
import sys
import tempfile

EXPECTED = {
    "cli_anything/gimp/__init__.py": "f11f248511257ac5f79dd98efca02bcef44ea4b4",
    "cli_anything/gimp/__main__.py": "133f5f9e4900184afbe3460abb3a32f9cc72b2eb",
    "cli_anything/gimp/core/__init__.py": "ab70e5ecb357339b0d5b5e5cf6c9dc2752d0b856",
    "cli_anything/gimp/core/canvas.py": "a958aa0df4facbbba20ca5b13fac018fb170f7c3",
    "cli_anything/gimp/core/export.py": "616531daf7710fe4f2602e9e6fd38295b651c53d",
    "cli_anything/gimp/core/filters.py": "d66aae27fce6fc0b2475ef565ebce689928fc927",
    "cli_anything/gimp/core/layers.py": "8a0d85f4dd1ec0a98e1cde422551b94700763996",
    "cli_anything/gimp/core/media.py": "3ec224c6ad74f458af8b78eff79707647c26b46d",
    "cli_anything/gimp/core/project.py": "d5065d5bab038adb8945767b08c036d74c76ba93",
    "cli_anything/gimp/core/session.py": "dc97c13e0104400ed5aa4221cf1b696e12c493b1",
    "cli_anything/gimp/gimp_cli.py": "ae039a3d14bb8134c5f64c0359d3bae930de814a",
    "cli_anything/gimp/utils/__init__.py": "51474dddd44e23d743b3d549fd35a6d1183f8156",
    "cli_anything/gimp/utils/gimp_backend.py": "6ce1c30c73a56914325a33c477a7db1e37e9f4b8",
    "cli_anything/gimp/utils/repl_skin.py": "bc1fb6d1d93fe6f46f97e8a0cd512ddc4371ad25"
}
root = Path(sys.argv[1]).resolve()
if (root / 'gimp/agent-harness').is_dir():
    root = root / 'gimp/agent-harness'
for relative, expected in EXPECTED.items():
    raw = (root / relative).read_bytes()
    actual = hashlib.sha1(b'blob ' + str(len(raw)).encode() + b'\0' + raw).hexdigest()
    assert actual == expected, relative
sys.path.insert(0, str(root))
from cli_anything.gimp.core.session import Session
from cli_anything.gimp.core.project import create_project, open_project
from cli_anything.gimp.core.layers import add_from_file
from cli_anything.gimp import gimp_cli
from click.testing import CliRunner
from PIL import Image

print(json.dumps({'python': platform.python_version(), 'click': importlib.metadata.version('click'),
                  'pillow': importlib.metadata.version('Pillow'), 'verifiedSourceFiles': len(EXPECTED)}))
with tempfile.TemporaryDirectory(prefix='cli-anything-state-') as directory:
    path = Path(directory) / 'project.gimp-cli.json'
    session = Session()
    session.set_project(create_project(width=16, height=16, name='before'))
    session.snapshot('rename')
    session.get_project()['name'] = 'after'
    session.undo()
    assert session.get_project()['name'] == 'before'
    session.redo()
    assert session.get_project()['name'] == 'after'
    before_save = session.status()['undo_count']
    session.save_session(str(path))
    saved = json.loads(path.read_text())
    reopened = Session()
    reopened.set_project(open_project(str(path)), str(path))
    assert before_save == 1 and reopened.status()['undo_count'] == 0
    assert reopened.get_project()['name'] == 'after'
    assert not {'_undo_stack', '_redo_stack', 'history'} & set(saved)
    try:
        reopened.undo()
        raise AssertionError('reopened session unexpectedly has history')
    except RuntimeError as error:
        assert str(error) == 'Nothing to undo.'
    print(json.dumps({'saveAndReopen': {'liveUndoPoints': before_save,
          'reopenedUndoPoints': reopened.status()['undo_count'], 'reopenedName': reopened.get_project()['name']}}))

    bounded = Session()
    bounded.set_project(create_project(name='v0'))
    for number in range(1, 56):
        bounded.snapshot(f'rename {number}')
        bounded.get_project()['name'] = f'v{number}'
    assert bounded.status()['undo_count'] == 50
    for _ in range(50):
        bounded.undo()
    assert bounded.get_project()['name'] == 'v5'
    print(json.dumps({'retention': {'mutations': 55, 'retainedUndoPoints': 50,
                                  'oldestReachableName': bounded.get_project()['name']}}))

    branched = Session()
    branched.set_project(create_project(name='a'))
    branched.snapshot('a to b')
    branched.get_project()['name'] = 'b'
    branched.undo()
    branched.snapshot('a to c')
    branched.get_project()['name'] = 'c'
    assert branched.status()['redo_count'] == 0
    print(json.dumps({'newBranchClearsRedo': True}))

    asset = Path(directory) / 'source.png'
    Image.new('RGB', (1, 1), (255, 0, 0)).save(asset)
    assets = Session()
    assets.set_project(create_project(width=1, height=1))
    add_from_file(assets.get_project(), str(asset))
    assets.snapshot('change opacity')
    assets.get_project()['layers'][0]['opacity'] = 0.25
    Image.new('RGB', (1, 1), (0, 0, 255)).save(asset)
    assets.undo()
    layer = assets.get_project()['layers'][0]
    with Image.open(layer['source']) as image:
        pixel = list(image.getpixel((0, 0)))
    assert layer['opacity'] == 1.0 and pixel == [0, 0, 255]
    print(json.dumps({'externalAsset': {'restoredOpacity': layer['opacity'], 'pixelAfterUndo': pixel}}))

    runner = CliRunner()
    cli_path = Path(directory) / 'cli.gimp-cli.json'
    gimp_cli._session = None
    created = runner.invoke(gimp_cli.cli, ['--json', 'project', 'new', '--output', str(cli_path)])
    added = runner.invoke(gimp_cli.cli, ['--json', 'layer', 'new', '--type', 'solid', '--name', 'Background'])
    undone = runner.invoke(gimp_cli.cli, ['--json', 'session', 'undo'])
    assert [created.exit_code, added.exit_code, undone.exit_code] == [0, 0, 0]
    assert gimp_cli.get_session().get_project()['layers'] == []
    print(json.dumps({'sharedProcessCommands': {'exitCodes': [0, 0, 0], 'layersAfterUndo': 0}}))

    # Re-create the layer, then replace only process-local session state.
    added_again = runner.invoke(gimp_cli.cli, ['--json', 'layer', 'new', '--type', 'solid'])
    assert added_again.exit_code == 0
    gimp_cli._session = None
    fresh = runner.invoke(gimp_cli.cli, ['--json', '--project', str(cli_path), 'session', 'undo'])
    assert fresh.exit_code == 1
    assert json.loads(fresh.output) == {'error': 'Nothing to undo.', 'type': 'RuntimeError'}
    assert len(json.loads(cli_path.read_text())['layers']) == 1
    print(json.dumps({'freshSessionCommand': {'exitCode': fresh.exit_code,
          'error': json.loads(fresh.output)['error'], 'persistedLayers': 1}}))
print('PASS: save/reopen, retention, redo branching, external assets, and command-session boundaries')
