import 'dart:typed_data';

import 'package:test/test.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_dart/wolf_3d_entities.dart';
import 'package:wolf_3d_dart/wolf_3d_input.dart';

void main() {
  test('SaveGameCodec round-trips a captured engine session snapshot', () {
    final WolfEngine engine = _buildEngine();
    engine.init();

    engine.player
      ..health = 80
      ..ammo = 40
      ..score = 900
      ..lives = 6
      ..hasMachineGun = true
      ..weapons[WeaponType.machineGun] = MachineGun()
      ..currentWeapon = MachineGun();

    final Guard guard =
        EntityRegistry.spawn(
                MapObject.guardStart,
                8.5,
                7.5,
                Difficulty.medium,
                engine.data.sprites.length,
                registry: engine.data.registry,
              )!
              as Guard
          ..health = 11
          ..state = EntityState.attacking;
    engine.entities = <Entity>[guard, SmallAmmoCollectible(x: 7.5, y: 6.5)];

    final GameSessionSnapshot snapshot = engine.captureSaveState();

    final SaveGameCodec codec = SaveGameCodec();
    final SaveGameFile file = SaveGameFile(
      slot: 0,
      gameVersion: engine.data.version,
      dataVersionName: engine.data.dataVersion.name,
      description: 'Unit Test Save',
      createdAtMs: 123456789,
      snapshot: snapshot,
      checksum: 0,
    );

    final Uint8List encoded = codec.encode(file);
    final SaveGameFile decoded = codec.decode(encoded);

    expect(decoded.slot, 0);
    expect(decoded.gameVersion, engine.data.version);
    expect(decoded.description, 'Unit Test Save');
    expect(decoded.snapshot.player.health, 80);
    expect(decoded.snapshot.player.currentWeaponType, WeaponType.machineGun);
    expect(decoded.snapshot.entities, hasLength(2));
    expect(decoded.snapshot.entities.first.kind, 'Guard');
    expect(decoded.snapshot.entities.first.state, EntityState.attacking);
  });

  test('SaveGameCodec rejects payloads with invalid checksum', () {
    final WolfEngine engine = _buildEngine();
    engine.init();

    final SaveGameCodec codec = SaveGameCodec();
    final Uint8List encoded = codec.encode(
      SaveGameFile(
        slot: 0,
        gameVersion: engine.data.version,
        dataVersionName: engine.data.dataVersion.name,
        description: 'Checksum Test',
        createdAtMs: 42,
        snapshot: engine.captureSaveState(),
        checksum: 0,
      ),
    );

    encoded[encoded.length - 1] ^= 0xFF;

    expect(
      () => codec.decode(encoded),
      throwsA(isA<FormatException>()),
    );
  });

  test('OriginalLayoutEnvelopeSaveGameCodec round-trips save metadata', () {
    final WolfEngine engine = _buildEngine();
    engine.init();

    final OriginalLayoutEnvelopeSaveGameCodec codec =
        OriginalLayoutEnvelopeSaveGameCodec();
    final SaveGameFile file = SaveGameFile(
      slot: 3,
      gameVersion: engine.data.version,
      dataVersionName: engine.data.dataVersion.name,
      description: 'Classic Envelope',
      createdAtMs: 999,
      snapshot: engine.captureSaveState(),
      checksum: 0,
    );

    final Uint8List encoded = codec.encode(file);
    final SaveGameFile decoded = codec.decode(encoded);

    expect(decoded.slot, 3);
    expect(decoded.description, 'Classic Envelope');
    expect(decoded.createdAtMs, 999);
    expect(decoded.gameVersion, engine.data.version);
    expect(
      decoded.snapshot.currentEpisodeIndex,
      file.snapshot.currentEpisodeIndex,
    );
    expect(decoded.snapshot.currentLevelIndex, file.snapshot.currentLevelIndex);
  });

  test('OriginalLayoutEnvelopeSaveGameCodec rejects invalid checksum', () {
    final WolfEngine engine = _buildEngine();
    engine.init();

    final OriginalLayoutEnvelopeSaveGameCodec codec =
        OriginalLayoutEnvelopeSaveGameCodec();
    final Uint8List encoded = codec.encode(
      SaveGameFile(
        slot: 0,
        gameVersion: engine.data.version,
        dataVersionName: engine.data.dataVersion.name,
        description: 'Classic Checksum',
        createdAtMs: 12,
        snapshot: engine.captureSaveState(),
        checksum: 0,
      ),
    );

    encoded[encoded.length - 1] ^= 0xFF;

    expect(
      () => codec.decode(encoded),
      throwsA(isA<FormatException>()),
    );
  });

  test('CompatibleSaveGameCodec decodes legacy W3DS saves', () {
    final WolfEngine engine = _buildEngine();
    engine.init();

    final SaveGameCodec legacyCodec = SaveGameCodec();
    final SaveGameFile file = SaveGameFile(
      slot: 1,
      gameVersion: engine.data.version,
      dataVersionName: engine.data.dataVersion.name,
      description: 'Legacy Save',
      createdAtMs: 777,
      snapshot: engine.captureSaveState(),
      checksum: 0,
    );
    final Uint8List legacyBytes = legacyCodec.encode(file);

    final CompatibleSaveGameCodec compatibleCodec = CompatibleSaveGameCodec();
    final SaveGameFile decoded = compatibleCodec.decode(legacyBytes);

    expect(decoded.slot, anyOf(0, 1));
    expect(decoded.description, 'Legacy Save');
    expect(decoded.createdAtMs, anyOf(0, 777));
  });

  test('CompatibleSaveGameCodec round-trips with block payload format', () {
    final WolfEngine engine = _buildEngine();
    engine.init();

    final CompatibleSaveGameCodec codec = CompatibleSaveGameCodec();
    final SaveGameFile file = SaveGameFile(
      slot: 2,
      gameVersion: engine.data.version,
      dataVersionName: engine.data.dataVersion.name,
      description: 'Compatible Block Save',
      createdAtMs: 1234,
      snapshot: engine.captureSaveState(),
      checksum: 0,
    );

    final Uint8List encoded = codec.encode(file);
    final SaveGameFile decoded = codec.decode(encoded);

    expect(decoded.slot, 2);
    expect(decoded.description, 'Compatible Block Save');
    expect(decoded.createdAtMs, 1234);
    expect(decoded.dataVersionName, file.dataVersionName);
    expect(
      decoded.snapshot.currentEpisodeIndex,
      file.snapshot.currentEpisodeIndex,
    );
    expect(decoded.snapshot.currentLevelIndex, file.snapshot.currentLevelIndex);
  });

  test('CompatibleSaveGameCodec preserves entity state fidelity', () {
    final WolfEngine engine = _buildEngine();
    engine.init();

    final Guard guard =
        EntityRegistry.spawn(
                MapObject.guardStart,
                8.5,
                7.5,
                Difficulty.medium,
                engine.data.sprites.length,
                registry: engine.data.registry,
              )!
              as Guard
          ..health = 5
          ..state = EntityState.dead;
    engine.entities = <Entity>[guard];

    final CompatibleSaveGameCodec codec = CompatibleSaveGameCodec();
    final SaveGameFile decoded = codec.decode(
      codec.encode(
        SaveGameFile(
          slot: 0,
          gameVersion: engine.data.version,
          dataVersionName: engine.data.dataVersion.name,
          description: 'Entity Fidelity',
          createdAtMs: 1,
          snapshot: engine.captureSaveState(),
          checksum: 0,
        ),
      ),
    );

    expect(decoded.snapshot.entities, hasLength(1));
    expect(decoded.snapshot.entities.first.kind, 'Guard');
    expect(decoded.snapshot.entities.first.state, EntityState.dead);
    expect(decoded.snapshot.entities.first.extraData['health'], 5);
  });

  test('CompatibleSaveGameCodec decodes old envelope payload format', () {
    final WolfEngine engine = _buildEngine();
    engine.init();

    final OriginalLayoutEnvelopeSaveGameCodec oldEnvelopeCodec =
        OriginalLayoutEnvelopeSaveGameCodec();
    final SaveGameFile file = SaveGameFile(
      slot: 4,
      gameVersion: engine.data.version,
      dataVersionName: engine.data.dataVersion.name,
      description: 'Old Envelope Save',
      createdAtMs: 333,
      snapshot: engine.captureSaveState(),
      checksum: 0,
    );
    final Uint8List oldEncoded = oldEnvelopeCodec.encode(file);

    final CompatibleSaveGameCodec compatibleCodec = CompatibleSaveGameCodec();
    final SaveGameFile decoded = compatibleCodec.decode(oldEncoded);

    expect(decoded.slot, anyOf(0, 4));
    expect(decoded.description, 'Old Envelope Save');
    expect(decoded.createdAtMs, anyOf(0, 333));
  });

  test(
    'CompatibleSaveGameCodec writes DOS-style description-prefixed files',
    () {
      final WolfEngine engine = _buildEngine();
      engine.init();

      final CompatibleSaveGameCodec codec = CompatibleSaveGameCodec();
      final SaveGameFile file = SaveGameFile(
        slot: 0,
        gameVersion: engine.data.version,
        dataVersionName: engine.data.dataVersion.name,
        description: 'DOS Layout Save',
        createdAtMs: 222,
        snapshot: engine.captureSaveState(),
        checksum: 0,
      );

      final Uint8List encoded = codec.encode(file);

      expect(encoded.length, greaterThan(32));
      expect(String.fromCharCodes(encoded.sublist(0, 3)), 'DOS');
      expect(
        encoded[32],
        isNot(equals(0x57)),
      ); // not WLFS signature at offset 32
    },
  );
}

class _TestInput extends Wolf3dInput {
  @override
  void update() {}
}

class _SilentAudio implements EngineAudio {
  @override
  WolfensteinData? activeGame;

  @override
  Future<void> debugSoundTest() async {}

  @override
  void dispose() {}

  @override
  Future<void> init() async {}

  @override
  void playLevelMusic(Music music) {}

  @override
  void playMenuMusic() {}

  @override
  void playSoundEffect(SoundEffect effect) {}

  @override
  void playSoundEffectId(int sfxId) {}

  @override
  Future<void> stopAllAudio() async {}

  @override
  void stopMusic() {}
}

WolfEngine _buildEngine() {
  final SpriteMap wallGrid = _buildGrid();
  final SpriteMap objectGrid = _buildGrid();
  _fillBoundaries(wallGrid, 2);

  objectGrid[2][2] = MapObject.playerEast;
  wallGrid[2][3] = 90;

  return WolfEngine(
    data: WolfensteinData(
      version: GameVersion.retail,
      dataVersion: DataVersion.unknown,
      registry: RetailAssetRegistry(),
      walls: <Sprite>[
        _solidSprite(1),
        _solidSprite(1),
        _solidSprite(2),
        _solidSprite(2),
      ],
      sprites: List<Sprite>.generate(436, (_) => _solidSprite(255)),
      sounds: List<PcmSound>.generate(200, (_) => PcmSound(Uint8List(1))),
      adLibSounds: const <PcmSound>[],
      music: const <ImfMusic>[],
      vgaImages: const <VgaImage>[],
      episodes: <Episode>[
        Episode(
          name: 'Episode 1',
          levels: <WolfLevel>[
            WolfLevel(
              name: 'Level 1',
              wallGrid: wallGrid,
              areaGrid: List<List<int>>.generate(
                64,
                (_) => List<int>.filled(64, -1),
                growable: false,
              ),
              objectGrid: objectGrid,
              music: Music.level01,
            ),
          ],
        ),
      ],
    ),
    difficulty: Difficulty.medium,
    startingEpisode: 0,
    frameBuffer: FrameBuffer(64, 64),
    input: _TestInput(),
    onGameWon: () {},
    engineAudio: _SilentAudio(),
  );
}

SpriteMap _buildGrid() =>
    List<List<int>>.generate(64, (_) => List<int>.filled(64, 0));

void _fillBoundaries(SpriteMap grid, int wallId) {
  for (int i = 0; i < 64; i++) {
    grid[0][i] = wallId;
    grid[63][i] = wallId;
    grid[i][0] = wallId;
    grid[i][63] = wallId;
  }
}

Sprite _solidSprite(int paletteIndex) {
  return Sprite(Uint8List.fromList(<int>[paletteIndex]));
}
