import 'dart:typed_data';

import 'package:test/test.dart';
import 'package:wolf_3d_dart/src/menu/menu_manager.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() {
  group('Level state carry-over', () {
    test('preserves player session state between levels but clears keys', () {
      final input = _TestInput();
      final engine = _buildEngine(input: input, difficulty: Difficulty.hard);

      engine.init();
      engine.player
        ..health = 47
        ..ammo = 33
        ..score = 1200
        ..lives = 5
        ..hasMachineGun = true
        ..hasChainGun = true
        ..hasGoldKey = true
        ..hasSilverKey = true;
      engine.player.weapons[WeaponType.machineGun] = MachineGun();
      engine.player.weapons[WeaponType.chainGun] = ChainGun();
      engine.player.currentWeapon = engine.player.weapons[WeaponType.chainGun]!;

      input.isInteracting = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isInteracting = false;

      expect(engine.activeLevel.name, 'Level 2');
      expect(engine.player.health, 47);
      expect(engine.player.ammo, 33);
      expect(engine.player.score, 1200);
      expect(engine.player.lives, 5);
      expect(engine.player.hasMachineGun, isTrue);
      expect(engine.player.hasChainGun, isTrue);
      expect(engine.player.currentWeapon.type, WeaponType.chainGun);
      expect(engine.player.hasGoldKey, isFalse);
      expect(engine.player.hasSilverKey, isFalse);
      expect(engine.player.x, closeTo(4.5, 0.001));
      expect(engine.player.y, closeTo(4.5, 0.001));
    });
  });

  group('Pause and main menu', () {
    test(
      'shows game selection before the shared main menu when multiple games exist',
      () {
        final input = _TestInput();
        final engine = _buildMultiGameEngine(input: input, difficulty: null);

        engine.init();
        _dismissIntroSplash(engine, input);

        expect(engine.isMenuOpen, isTrue);
        expect(engine.menuManager.activeMenu, WolfMenuScreen.gameSelect);

        input.isInteracting = true;
        engine.tick(const Duration(milliseconds: 16));
        input.isInteracting = false;
        engine.tick(const Duration(milliseconds: 300));
        _dismissIntroSplash(engine, input);

        expect(engine.menuManager.activeMenu, WolfMenuScreen.mainMenu);
        expect(
          engine.menuManager.mainMenuEntries
              .map((entry) => entry.label)
              .toList(),
          [
            'NEW GAME',
            'SOUND',
            'CONTROL',
            'LOAD GAME',
            'SAVE GAME',
            'CHANGE VIEW',
            'READ THIS!',
            'VIEW SCORES',
            'BACK TO DEMO',
            'QUIT',
          ],
        );
        expect(
          engine.menuManager.mainMenuEntries
              .map((entry) => entry.isEnabled)
              .toList(),
          [true, false, false, false, false, true, false, false, true, true],
        );

        input.isInteracting = true;
        engine.tick(const Duration(milliseconds: 16));
        input.isInteracting = false;
        engine.tick(const Duration(milliseconds: 300));

        expect(engine.menuManager.activeMenu, WolfMenuScreen.episodeSelect);
      },
    );

    test('single-game startup opens the shared main menu directly', () {
      final input = _TestInput();
      final engine = _buildEngine(input: input, difficulty: null);

      engine.init();
      _advanceToMainMenu(engine, input);

      expect(engine.isMenuOpen, isTrue);
      expect(engine.menuManager.activeMenu, WolfMenuScreen.mainMenu);
      expect(
        engine.menuManager.mainMenuEntries.map((entry) => entry.label).toList(),
        [
          'NEW GAME',
          'SOUND',
          'CONTROL',
          'LOAD GAME',
          'SAVE GAME',
          'CHANGE VIEW',
          'READ THIS!',
          'VIEW SCORES',
          'BACK TO DEMO',
          'QUIT',
        ],
      );
      expect(
        engine.menuManager.mainMenuEntries
            .map((entry) => entry.isEnabled)
            .toList(),
        [true, false, false, false, false, true, false, false, true, true],
      );

      input.isInteracting = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isInteracting = false;
      engine.tick(const Duration(milliseconds: 300));

      expect(engine.menuManager.activeMenu, WolfMenuScreen.episodeSelect);
    });

    test(
      'escape opens pause menu and back to game resumes without resetting state',
      () {
        final input = _TestInput();
        final engine = _buildEngine(input: input, difficulty: Difficulty.hard);

        engine.init();
        final double startX = engine.player.x;
        final double startY = engine.player.y;

        input.isBack = true;
        engine.tick(const Duration(milliseconds: 16));
        input.isBack = false;

        expect(engine.isMenuOpen, isTrue);
        expect(engine.menuManager.activeMenu, WolfMenuScreen.mainMenu);
        expect(
          engine.menuManager.mainMenuEntries
              .map((entry) => entry.label)
              .toList(),
          [
            'NEW GAME',
            'SOUND',
            'CONTROL',
            'LOAD GAME',
            'SAVE GAME',
            'CHANGE VIEW',
            'READ THIS!',
            'END GAME',
            'BACK TO GAME',
            'QUIT',
          ],
        );
        expect(
          engine.menuManager.mainMenuEntries
              .map((entry) => entry.isEnabled)
              .toList(),
          [true, false, false, false, false, true, false, true, true, true],
        );

        input.isMovingForward = true;
        engine.tick(const Duration(milliseconds: 16));
        expect(engine.player.x, closeTo(startX, 0.001));
        expect(engine.player.y, closeTo(startY, 0.001));

        input
          ..isMovingForward = false
          ..isBack = true;
        engine.tick(const Duration(milliseconds: 16));
        input.isBack = false;

        expect(engine.isMenuOpen, isFalse);

        input.isMovingForward = true;
        engine.tick(const Duration(milliseconds: 16));
        input.isMovingForward = false;

        expect(engine.player.x, greaterThan(startX));
      },
    );

    test('main menu skips disabled entries during navigation', () {
      final manager = MenuManager();

      manager.showMainMenu(hasResumableGame: false);

      expect(manager.selectedMainIndex, 0);

      manager.updateMainMenu(const EngineInput(isMovingBackward: true));
      manager.updateMainMenu(const EngineInput());
      expect(manager.selectedMainIndex, 5);

      manager.updateMainMenu(const EngineInput(isMovingBackward: true));
      manager.updateMainMenu(const EngineInput());
      expect(manager.selectedMainIndex, 8);

      manager.updateMainMenu(const EngineInput(isMovingBackward: true));
      manager.updateMainMenu(const EngineInput());
      expect(manager.selectedMainIndex, 9);

      manager.updateMainMenu(const EngineInput(isMovingBackward: true));
      manager.updateMainMenu(const EngineInput());
      expect(manager.selectedMainIndex, 0);
    });

    test('quit selection triggers dedicated quit callback', () {
      final input = _TestInput();
      int quitCalls = 0;
      int exitCalls = 0;
      final engine = _buildEngine(
        input: input,
        difficulty: null,
        onQuit: () {
          quitCalls++;
        },
        onMenuExit: () {
          exitCalls++;
        },
      );

      engine.init();
      _advanceToMainMenu(engine, input);

      input.isMovingBackward = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isMovingBackward = false;
      engine.tick(const Duration(milliseconds: 16));

      input.isMovingBackward = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isMovingBackward = false;
      engine.tick(const Duration(milliseconds: 16));

      input.isMovingBackward = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isMovingBackward = false;
      engine.tick(const Duration(milliseconds: 16));

      expect(engine.menuManager.selectedMainIndex, 9);

      input.isInteracting = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isInteracting = false;

      expect(quitCalls, 1);
      expect(exitCalls, 0);
    });

    test('backing out of the top-level menu uses menu-exit callback', () {
      final input = _TestInput();
      int quitCalls = 0;
      int exitCalls = 0;
      final engine = _buildEngine(
        input: input,
        difficulty: null,
        onQuit: () {
          quitCalls++;
        },
        onMenuExit: () {
          exitCalls++;
        },
      );

      engine.init();
      _advanceToMainMenu(engine, input);

      input.isBack = true;
      engine.tick(const Duration(milliseconds: 16));
      input.isBack = false;

      expect(quitCalls, 0);
      expect(exitCalls, 1);
    });
  });
}

WolfEngine _buildMultiGameEngine({
  required _TestInput input,
  required Difficulty? difficulty,
  void Function()? onMenuExit,
  void Function()? onQuit,
}) {
  final WolfensteinData retail = _buildTestData(
    gameVersion: GameVersion.retail,
  );
  final WolfensteinData shareware = _buildTestData(
    gameVersion: GameVersion.shareware,
  );

  return WolfEngine(
    availableGames: [retail, shareware],
    difficulty: difficulty,
    startingEpisode: 0,
    frameBuffer: FrameBuffer(64, 64),
    input: input,
    engineAudio: _SilentAudio(),
    onGameWon: () {},
    onMenuExit: onMenuExit,
    onQuit: onQuit,
  );
}

WolfEngine _buildEngine({
  required _TestInput input,
  required Difficulty? difficulty,
  void Function()? onMenuExit,
  void Function()? onQuit,
}) {
  return WolfEngine(
    data: _buildTestData(gameVersion: GameVersion.retail),
    difficulty: difficulty,
    startingEpisode: 0,
    frameBuffer: FrameBuffer(64, 64),
    input: input,
    engineAudio: _SilentAudio(),
    onGameWon: () {},
    onMenuExit: onMenuExit,
    onQuit: onQuit,
  );
}

WolfensteinData _buildTestData({required GameVersion gameVersion}) {
  final levelOneWalls = _buildGrid();
  final levelOneObjects = _buildGrid();
  final levelTwoWalls = _buildGrid();
  final levelTwoObjects = _buildGrid();

  _fillBoundaries(levelOneWalls, 2);
  _fillBoundaries(levelTwoWalls, 2);

  levelOneObjects[2][2] = MapObject.playerEast;
  levelOneWalls[2][3] = MapObject.normalElevatorSwitch;

  levelTwoObjects[4][4] = MapObject.playerEast;

  return WolfensteinData(
    version: gameVersion,
    dataVersion: DataVersion.unknown,
    registry: gameVersion == GameVersion.shareware
        ? SharewareAssetRegistry()
        : RetailAssetRegistry(),
    walls: [
      _solidSprite(1),
      _solidSprite(1),
      _solidSprite(2),
      _solidSprite(2),
    ],
    sprites: List.generate(436, (_) => _solidSprite(255)),
    sounds: List.generate(200, (_) => PcmSound(Uint8List(1))),
    adLibSounds: const [],
    music: const [],
    vgaImages: const [],
    episodes: [
      Episode(
        name: 'Episode 1',
        levels: [
          WolfLevel(
            name: 'Level 1',
            wallGrid: levelOneWalls,
            areaGrid: List.generate(64, (_) => List.filled(64, -1)),
            objectGrid: levelOneObjects,
            music: Music.level01,
          ),
          WolfLevel(
            name: 'Level 2',
            wallGrid: levelTwoWalls,
            areaGrid: List.generate(64, (_) => List.filled(64, -1)),
            objectGrid: levelTwoObjects,
            music: Music.level02,
          ),
        ],
      ),
    ],
  );
}

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

class _SilentAudio implements EngineAudio {
  @override
  WolfensteinData? activeGame;

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

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

  @override
  void playLevelMusic(Music music) {}

  @override
  void playMenuMusic() {}

  @override
  void playSoundEffect(SoundEffect effect) {}

  @override
  void playSoundEffectId(int sfxId) {}

  @override
  void stopMusic() {}

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

  @override
  void dispose() {}
}

void _dismissIntroSplash(WolfEngine engine, _TestInput input) {
  int safety = 0;
  while (engine.menuManager.activeMenu == WolfMenuScreen.introSplash &&
      safety < 160) {
    input.isInteracting = safety.isEven;
    engine.tick(const Duration(milliseconds: 16));
    safety++;
  }
  input.isInteracting = false;
}

void _advanceToMainMenu(WolfEngine engine, _TestInput input) {
  _dismissIntroSplash(engine, input);
  if (engine.menuManager.activeMenu == WolfMenuScreen.gameSelect) {
    input.isInteracting = true;
    engine.tick(const Duration(milliseconds: 16));
    input.isInteracting = false;
    engine.tick(const Duration(milliseconds: 300));
  }
  expect(engine.menuManager.activeMenu, WolfMenuScreen.mainMenu);
}

SpriteMap _buildGrid() => List.generate(64, (_) => List.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 colorIndex) {
  return Sprite(Uint8List.fromList(List.filled(64 * 64, colorIndex)));
}
