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() {
  group('Engine audio events', () {
    test('plays pickup sound effect when player collects ammo', () {
      final input = _TestInput();
      final audio = _CapturingAudio();
      final engine = _buildEngine(
        input: input,
        audio: audio,
        objectGridSetup: (grid) {
          grid[2][2] = MapObject.playerEast;
        },
      );

      engine.init();
      engine.entities.add(
        AmmoCollectible(x: engine.player.x, y: engine.player.y),
      );
      engine.tick(const Duration(milliseconds: 16));

      expect(audio.sfxIds, contains(SoundEffect.getAmmo));
    });

    test('plays guard alert when guard notices player', () {
      final input = _TestInput();
      final audio = _CapturingAudio();
      final engine = _buildEngine(
        input: input,
        audio: audio,
        objectGridSetup: (grid) {
          grid[2][2] = MapObject.playerEast;
          grid[2][5] = MapObject.guardStart + 2;
        },
      );

      engine.init();

      // Wake-up delay is randomized, so tick long enough to always exceed it.
      for (int i = 0; i < 80; i++) {
        engine.tick(const Duration(milliseconds: 16));
      }

      expect(audio.sfxIds, contains(SoundEffect.guardHalt));
    });

    test('plays dog death sound when dog dies', () {
      final input = _TestInput();
      final audio = _CapturingAudio();
      final engine = _buildEngine(
        input: input,
        audio: audio,
        objectGridSetup: (grid) {
          grid[2][2] = MapObject.playerEast;
          grid[2][4] = MapObject.dogStart;
        },
      );

      engine.init();

      final dog = engine.entities.whereType<Dog>().single;
      dog.takeDamage(999, 0);

      engine.tick(const Duration(milliseconds: 16));

      expect(audio.sfxIds, contains(SoundEffect.dogDeath));
    });

    test('plays pushwall sound when triggering a secret wall', () {
      final input = _TestInput()..isInteracting = true;
      final audio = _CapturingAudio();
      final engine = _buildEngine(
        input: input,
        audio: audio,
        wallGridSetup: (grid) {
          grid[2][3] = 1;
        },
        objectGridSetup: (grid) {
          grid[2][2] = MapObject.playerEast;
          grid[2][3] = MapObject.pushwallTrigger;
        },
      );

      engine.init();
      engine.tick(const Duration(milliseconds: 16));

      expect(audio.sfxIds, contains(SoundEffect.pushWall));
    });
  });
}

/// Builds a minimal deterministic engine world for audio event tests.
WolfEngine _buildEngine({
  required _TestInput input,
  required _CapturingAudio audio,
  void Function(SpriteMap grid)? wallGridSetup,
  void Function(SpriteMap grid)? objectGridSetup,
}) {
  final wallGrid = _buildGrid();
  final objectGrid = _buildGrid();

  _fillBoundaries(wallGrid, 2);
  wallGridSetup?.call(wallGrid);
  objectGridSetup?.call(objectGrid);

  return WolfEngine(
    data: WolfensteinData(
      version: GameVersion.retail,
      dataVersion: DataVersion.unknown,
      registry: 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: 'Test Level',
              wallGrid: wallGrid,
              areaGrid: List.generate(64, (_) => List.filled(64, -1)),
              objectGrid: objectGrid,
              music: Music.level01,
            ),
          ],
        ),
      ],
    ),
    difficulty: Difficulty.hard,
    startingEpisode: 0,
    frameBuffer: FrameBuffer(64, 64),
    input: input,
    engineAudio: audio,
    onGameWon: () {},
  );
}

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

/// Captures engine SFX IDs so tests can assert dispatch behavior.
class _CapturingAudio implements EngineAudio {
  @override
  WolfensteinData? activeGame;

  final List<SoundEffect> sfxIds = [];

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

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

  @override
  void playLevelMusic(Music music) {}

  @override
  void playMenuMusic() {}

  @override
  void playSoundEffect(SoundEffect effect) {
    sfxIds.add(effect);
  }

  @override
  void playSoundEffectId(int sfxId) {
    final effect = SoundEffect.values
        .where((entry) => entry.retailId == sfxId)
        .firstOrNull;
    if (effect != null) {
      sfxIds.add(effect);
    }
  }

  @override
  void stopMusic() {}

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

  @override
  void dispose() {}
}

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

/// Fills the map borders so entities never walk off the test map.
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;
  }
}

/// Produces a simple solid-color sprite frame for synthetic test assets.
Sprite _solidSprite(int colorIndex) {
  return Sprite(Uint8List.fromList(List.filled(64 * 64, colorIndex)));
}
