// test/unit/voice_command_handler_test.dart
//
// Unit test untuk VoiceCommandHandler — matching & dispatching command.
// Jalankan: flutter test test/unit/voice_command_handler_test.dart
//
// Pure Dart test, tidak butuh platform plugin.

import 'package:flutter_test/flutter_test.dart';

// ---------- Stubs (mirror dari project asli) ----------

enum VoiceCommandKey {
  openWalkguide,
  startWalkguide,
  stopWalkguide,
  callGuardian,
  openNotification,
  readAllNotif,
  openSos,
  sendSos,
  whereAmI,
  openActivity,
  openNavigation,
  openSettings,
  repeatLast,
  stopTts,
}

class VoiceCommand {
  final VoiceCommandKey key;
  final String phrase;
  final bool enabled;
  const VoiceCommand(
      {required this.key, required this.phrase, required this.enabled});
}

// Minimal TtsService stub
class _FakeTts {
  String? lastSpeech;
  bool stopped = false;
  void repeatLast() => lastSpeech = 'repeated';
  void stop() => stopped = true;
}

// Minimal SttService stub
class _FakeStt {
  void Function(String)? onResult;
  void simulateUtterance(String text) => onResult?.call(text);
}

// VoiceCommandHandler (mirror dari project)
typedef CommandCallback = void Function(VoiceCommandKey key);

class _VoiceCommandHandler {
  final _FakeStt _stt;
  final _FakeTts _tts;

  List<VoiceCommand> _commands = [];
  CommandCallback? onCommand;

  _VoiceCommandHandler(this._stt, this._tts);

  void loadCommands(List<VoiceCommand> commands) {
    _commands = commands;
    _stt.onResult = _processText;
  }

  void loadDefaultCommands() {
    _commands = const [
      VoiceCommand(
          key: VoiceCommandKey.openWalkguide,
          phrase: 'open walkguide',
          enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.startWalkguide,
          phrase: 'start walkguide',
          enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.stopWalkguide,
          phrase: 'stop walkguide',
          enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.callGuardian,
          phrase: 'call guardian',
          enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.openNotification,
          phrase: 'open notifications',
          enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.readAllNotif,
          phrase: 'read all my notifications',
          enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.openSos, phrase: 'open sos', enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.sendSos, phrase: 'send sos', enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.whereAmI, phrase: 'where am i', enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.openActivity,
          phrase: 'open activity log',
          enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.openNavigation,
          phrase: 'open navigation',
          enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.openSettings,
          phrase: 'open settings',
          enabled: true),
      VoiceCommand(
          key: VoiceCommandKey.repeatLast, phrase: 'repeat', enabled: true),
      VoiceCommand(key: VoiceCommandKey.stopTts, phrase: 'stop', enabled: true),
    ];
    _stt.onResult = _processText;
  }

  void _processText(String text) {
    final lower = text.toLowerCase().trim();
    for (final cmd in _commands) {
      if (!cmd.enabled) continue;
      if (lower.contains(cmd.phrase.toLowerCase())) {
        _handleCommand(cmd.key);
        return;
      }
    }
  }

  void _handleCommand(VoiceCommandKey key) {
    onCommand?.call(key);
    if (key == VoiceCommandKey.repeatLast) _tts.repeatLast();
    if (key == VoiceCommandKey.stopTts) _tts.stop();
  }
}

// ---------- Tests ----------

void main() {
  late _VoiceCommandHandler handler;
  late _FakeStt fakeStt;
  late _FakeTts fakeTts;

  final List<VoiceCommandKey> dispatchedCommands = [];

  setUp(() {
    fakeStt = _FakeStt();
    fakeTts = _FakeTts();
    handler = _VoiceCommandHandler(fakeStt, fakeTts);
    dispatchedCommands.clear();
    handler.loadDefaultCommands();
    handler.onCommand = dispatchedCommands.add;
  });

  // ── BASIC COMMAND MATCHING ────────────────────────────────────────

  group('default commands — exact phrase match', () {
    test('"start walkguide" → startWalkguide', () {
      fakeStt.simulateUtterance('start walkguide');
      expect(dispatchedCommands, contains(VoiceCommandKey.startWalkguide));
    });

    test('"stop walkguide" → stopWalkguide', () {
      fakeStt.simulateUtterance('stop walkguide');
      expect(dispatchedCommands, contains(VoiceCommandKey.stopWalkguide));
    });

    test('"call guardian" → callGuardian', () {
      fakeStt.simulateUtterance('call guardian');
      expect(dispatchedCommands, contains(VoiceCommandKey.callGuardian));
    });

    test('"open notifications" → openNotification', () {
      fakeStt.simulateUtterance('open notifications');
      expect(dispatchedCommands, contains(VoiceCommandKey.openNotification));
    });

    test('"send sos" → sendSos', () {
      fakeStt.simulateUtterance('send sos');
      expect(dispatchedCommands, contains(VoiceCommandKey.sendSos));
    });

    test('"where am i" → whereAmI', () {
      fakeStt.simulateUtterance('where am i');
      expect(dispatchedCommands, contains(VoiceCommandKey.whereAmI));
    });

    test('"open activity log" → openActivity', () {
      fakeStt.simulateUtterance('open activity log');
      expect(dispatchedCommands, contains(VoiceCommandKey.openActivity));
    });

    test('"open navigation" → openNavigation', () {
      fakeStt.simulateUtterance('open navigation');
      expect(dispatchedCommands, contains(VoiceCommandKey.openNavigation));
    });

    test('"open settings" → openSettings', () {
      fakeStt.simulateUtterance('open settings');
      expect(dispatchedCommands, contains(VoiceCommandKey.openSettings));
    });

    test('"read all my notifications" → readAllNotif', () {
      fakeStt.simulateUtterance('read all my notifications');
      expect(dispatchedCommands, contains(VoiceCommandKey.readAllNotif));
    });
  });

  // ── CASE INSENSITIVITY ────────────────────────────────────────────

  group('case insensitive matching', () {
    test('"START WALKGUIDE" → startWalkguide', () {
      fakeStt.simulateUtterance('START WALKGUIDE');
      expect(dispatchedCommands, contains(VoiceCommandKey.startWalkguide));
    });

    test('"Start Walkguide" → startWalkguide', () {
      fakeStt.simulateUtterance('Start Walkguide');
      expect(dispatchedCommands, contains(VoiceCommandKey.startWalkguide));
    });

    test('"CALL GUARDIAN" → callGuardian', () {
      fakeStt.simulateUtterance('CALL GUARDIAN');
      expect(dispatchedCommands, contains(VoiceCommandKey.callGuardian));
    });

    test('"Send SOS" → sendSos', () {
      fakeStt.simulateUtterance('Send SOS');
      expect(dispatchedCommands, contains(VoiceCommandKey.sendSos));
    });
  });

  // ── PHRASE CONTAINS MATCH (dari kalimat lebih panjang) ───────────

  group('phrase contains match', () {
    test('kalimat panjang mengandung "start walkguide" → startWalkguide', () {
      fakeStt.simulateUtterance('tolong start walkguide sekarang');
      expect(dispatchedCommands, contains(VoiceCommandKey.startWalkguide));
    });

    test('kalimat panjang mengandung "send sos" → sendSos', () {
      fakeStt.simulateUtterance('saya mau send sos ke guardian');
      expect(dispatchedCommands, contains(VoiceCommandKey.sendSos));
    });

    test('kalimat panjang mengandung "call guardian" → callGuardian', () {
      fakeStt.simulateUtterance('bisa tolong call guardian untuk aku');
      expect(dispatchedCommands, contains(VoiceCommandKey.callGuardian));
    });
  });

  // ── NO MATCH ──────────────────────────────────────────────────────

  group('unrecognized input tidak memicu command', () {
    test('kata random tidak memicu command', () {
      fakeStt.simulateUtterance('halo apa kabar');
      expect(dispatchedCommands, isEmpty);
    });

    test('string kosong tidak memicu command', () {
      fakeStt.simulateUtterance('');
      expect(dispatchedCommands, isEmpty);
    });

    test('kata mirip tapi bukan phrase yang valid tidak match', () {
      fakeStt.simulateUtterance('starting guide');
      expect(dispatchedCommands, isEmpty);
    });

    test('spasi saja tidak memicu command', () {
      fakeStt.simulateUtterance('   ');
      expect(dispatchedCommands, isEmpty);
    });
  });

  // ── BUILT-IN TTS ACTIONS ─────────────────────────────────────────

  group('built-in TTS actions', () {
    test('"repeat" → memanggil tts.repeatLast()', () {
      fakeStt.simulateUtterance('repeat');
      expect(fakeTts.lastSpeech, 'repeated');
      expect(dispatchedCommands, contains(VoiceCommandKey.repeatLast));
    });

    test('"stop" → memanggil tts.stop()', () {
      fakeStt.simulateUtterance('stop');
      expect(fakeTts.stopped, true);
      expect(dispatchedCommands, contains(VoiceCommandKey.stopTts));
    });
  });

  // ── DISABLED COMMAND ─────────────────────────────────────────────

  group('disabled command tidak memicu callback', () {
    test('command disabled tidak di-dispatch', () {
      handler.loadCommands([
        const VoiceCommand(
            key: VoiceCommandKey.sendSos, phrase: 'send sos', enabled: false),
        const VoiceCommand(
            key: VoiceCommandKey.callGuardian,
            phrase: 'call guardian',
            enabled: true),
      ]);

      fakeStt.simulateUtterance('send sos');
      expect(dispatchedCommands, isEmpty);
    });

    test('command enabled di-dispatch, disabled tidak', () {
      handler.loadCommands([
        const VoiceCommand(
            key: VoiceCommandKey.sendSos, phrase: 'send sos', enabled: false),
        const VoiceCommand(
            key: VoiceCommandKey.callGuardian,
            phrase: 'call guardian',
            enabled: true),
      ]);

      fakeStt.simulateUtterance('call guardian');
      expect(dispatchedCommands, contains(VoiceCommandKey.callGuardian));
      expect(dispatchedCommands, isNot(contains(VoiceCommandKey.sendSos)));
    });
  });

  // ── CUSTOM COMMANDS ───────────────────────────────────────────────

  group('loadCommands dengan custom phrase', () {
    test('custom phrase "mulai jalan" → startWalkguide', () {
      handler.loadCommands([
        const VoiceCommand(
            key: VoiceCommandKey.startWalkguide,
            phrase: 'mulai jalan',
            enabled: true),
      ]);
      fakeStt.simulateUtterance('mulai jalan');
      expect(dispatchedCommands, contains(VoiceCommandKey.startWalkguide));
    });

    test('custom phrase "darurat" → sendSos', () {
      handler.loadCommands([
        const VoiceCommand(
            key: VoiceCommandKey.sendSos, phrase: 'darurat', enabled: true),
      ]);
      fakeStt.simulateUtterance('ini darurat tolong');
      expect(dispatchedCommands, contains(VoiceCommandKey.sendSos));
    });
  });

  // ── FIRST MATCH WINS ─────────────────────────────────────────────

  group('hanya satu command yang dipicu per utterance', () {
    test('hanya command pertama yang match yang dipicu', () {
      fakeStt.simulateUtterance('stop walkguide now');
      // "stop walkguide" match stopWalkguide, tapi "stop" juga ada di list
      // Urutan di default list: stopWalkguide (index 2) sebelum stopTts (index 13)
      expect(dispatchedCommands.length, 1);
    });
  });
}
