import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:christian_period_tracker/models/scripture.dart';
import 'package:christian_period_tracker/models/cycle_entry.dart';
import 'package:christian_period_tracker/models/user_profile.dart';
import 'package:christian_period_tracker/providers/scripture_provider.dart';
import 'package:christian_period_tracker/providers/user_provider.dart';
import 'package:hive_flutter/hive_flutter.dart';

// Fake ScriptureDatabase implementation for testing
class FakeScriptureDatabase implements ScriptureDatabase {
  final int Function(String phase) getScriptureCountForPhaseFn;
  final Scripture? Function(String phase, int index)
      getScriptureForPhaseByIndexFn;
  final Scripture? Function(String phase)? getRandomScriptureForPhaseFn;

  FakeScriptureDatabase({
    required this.getScriptureCountForPhaseFn,
    required this.getScriptureForPhaseByIndexFn,
    this.getRandomScriptureForPhaseFn,
  });

  @override
  int getScriptureCountForPhase(String phase) =>
      getScriptureCountForPhaseFn(phase);

  @override
  Scripture? getScriptureForPhaseByIndex(String phase, int index) =>
      getScriptureForPhaseByIndexFn(phase, index);

  @override
  Scripture? getRandomScriptureForPhase(String phase) =>
      getRandomScriptureForPhaseFn != null
          ? getRandomScriptureForPhaseFn!(phase)
          : null;

  // Unimplemented methods (not used by ScriptureNotifier)
  @override
  List<Scripture> getAllScriptures() => throw UnimplementedError();

  @override
  Scripture getHusbandScripture() => throw UnimplementedError();

  @override
  Future<void> loadScriptures() =>
      Future.value(); // Can be mocked to do nothing

  @override
  Scripture? getRecommendedScripture(CycleEntry entry) =>
      throw UnimplementedError();

  @override
  Scripture getScriptureForPhase(String phase) => throw UnimplementedError();
}

void main() {
  group('ScriptureNotifier', () {
    late ProviderContainer container;
    late String testPath;

    setUpAll(() async {
      testPath = '${Directory.current.path}/test_hive_temp_scripture_provider';
      final Directory tempDir = Directory(testPath);
      if (!await tempDir.exists()) {
        await tempDir.create(recursive: true);
      }
      Hive.init(testPath);
      Hive.registerAdapter(UserProfileAdapter());
      Hive.registerAdapter(RelationshipStatusAdapter());
      Hive.registerAdapter(FertilityGoalAdapter());
      Hive.registerAdapter(BibleTranslationAdapter());
      Hive.registerAdapter(UserRoleAdapter());
      await Hive.openBox<UserProfile>('user_profile');
    });

    tearDownAll(() async {
      await Hive.close();
      await Directory(testPath).delete(recursive: true);
    });

    final testScripture1 = Scripture(
      verses: {BibleTranslation.esv: "Verse 1"},
      reference: "Ref 1",
      applicablePhases: ['menstrual'],
    );
    final testScripture2 = Scripture(
      verses: {BibleTranslation.esv: "Verse 2"},
      reference: "Ref 2",
      applicablePhases: ['menstrual'],
    );
    final testScripture3 = Scripture(
      verses: {BibleTranslation.esv: "Verse 3"},
      reference: "Ref 3",
      applicablePhases: ['menstrual'],
    );

    tearDown(() {
      container.dispose();
    });

    test('initializes with correct scripture for phase', () async {
      final fakeDb = FakeScriptureDatabase(
        getScriptureCountForPhaseFn: (phase) => 3,
        getScriptureForPhaseByIndexFn: (phase, index) => testScripture1,
      );

      container = ProviderContainer(
        overrides: [
          scriptureDatabaseProvider.overrideWithValue(fakeDb),
          userProfileProvider.overrideWith(
            (ref) => UserProfileNotifier(),
          ),
        ],
      );

      final notifier = container.read(scriptureProvider.notifier);
      notifier.initializeScripture(CyclePhase.menstrual);

      final state = container.read(scriptureProvider);
      expect(state.currentScripture, testScripture1);
      expect(state.currentPhase, CyclePhase.menstrual);
      expect(state.maxIndex, 3);
      // currentIndex will depend on dayOfYear % 3, which is hard to predict
      // So we'll just check it's within bounds.
      expect(state.currentIndex, isNonNegative);
      expect(state.currentIndex, lessThan(3));
    });

    test('getNextScripture cycles correctly', () async {
      final scriptures = [testScripture1, testScripture2, testScripture3];
      final fakeDb = FakeScriptureDatabase(
        getScriptureCountForPhaseFn: (phase) => scriptures.length,
        getScriptureForPhaseByIndexFn: (phase, index) => scriptures[index],
      );

      container = ProviderContainer(
        overrides: [
          scriptureDatabaseProvider.overrideWithValue(fakeDb),
          userProfileProvider.overrideWith(
            (ref) => UserProfileNotifier(),
          ),
        ],
      );

      final notifier = container.read(scriptureProvider.notifier);
      // Force initial state to 0 for predictable cycling
      notifier.initializeScripture(CyclePhase.menstrual);
      // Override currentIndex to 0 for predictable test
      container.read(scriptureProvider.notifier).state =
          container.read(scriptureProvider).copyWith(currentIndex: 0);

      // First next
      notifier.getNextScripture();
      expect(
          container.read(scriptureProvider).currentScripture, testScripture2);
      expect(container.read(scriptureProvider).currentIndex, 1);

      // Second next
      notifier.getNextScripture();
      expect(
          container.read(scriptureProvider).currentScripture, testScripture3);
      expect(container.read(scriptureProvider).currentIndex, 2);

      // Wrap around
      notifier.getNextScripture();
      expect(
          container.read(scriptureProvider).currentScripture, testScripture1);
      expect(container.read(scriptureProvider).currentIndex, 0);
    });

    test('getPreviousScripture cycles correctly', () async {
      final scriptures = [testScripture1, testScripture2, testScripture3];
      final fakeDb = FakeScriptureDatabase(
        getScriptureCountForPhaseFn: (phase) => scriptures.length,
        getScriptureForPhaseByIndexFn: (phase, index) => scriptures[index],
      );

      container = ProviderContainer(
        overrides: [
          scriptureDatabaseProvider.overrideWithValue(fakeDb),
          userProfileProvider.overrideWith(
            (ref) => UserProfileNotifier(),
          ),
        ],
      );

      final notifier = container.read(scriptureProvider.notifier);
      // Force initial state to 0 for predictable cycling
      notifier.initializeScripture(CyclePhase.menstrual);
      // Override currentIndex to 0 for predictable test
      container.read(scriptureProvider.notifier).state =
          container.read(scriptureProvider).copyWith(currentIndex: 0);

      // First previous (wraps around)
      notifier.getPreviousScripture();
      expect(
          container.read(scriptureProvider).currentScripture, testScripture3);
      expect(container.read(scriptureProvider).currentIndex, 2);

      // Second previous
      notifier.getPreviousScripture();
      expect(
          container.read(scriptureProvider).currentScripture, testScripture2);
      expect(container.read(scriptureProvider).currentIndex, 1);

      // Third previous
      notifier.getPreviousScripture();
      expect(
          container.read(scriptureProvider).currentScripture, testScripture1);
      expect(container.read(scriptureProvider).currentIndex, 0);
    });

    test('getRandomScripture updates to a valid scripture', () async {
      final scriptures = [testScripture1, testScripture2, testScripture3];
      final fakeDb = FakeScriptureDatabase(
        getScriptureCountForPhaseFn: (phase) => scriptures.length,
        getScriptureForPhaseByIndexFn: (phase, index) => scriptures[
            index % scriptures.length], // Ensure it always returns a valid one
      );

      container = ProviderContainer(
        overrides: [
          scriptureDatabaseProvider.overrideWithValue(fakeDb),
          userProfileProvider.overrideWith(
            (ref) => UserProfileNotifier(),
          ),
        ],
      );

      final notifier = container.read(scriptureProvider.notifier);
      notifier.initializeScripture(CyclePhase.menstrual);

      // Perform a random selection
      notifier.getRandomScripture();
      final state = container.read(scriptureProvider);

      expect(state.currentScripture, isNotNull);
      expect(state.currentScripture,
          isIn(scriptures)); // Ensure it's one of the valid scriptures
      expect(state.currentIndex, isNonNegative);
      expect(state.currentIndex, lessThan(scriptures.length));
    });

    test('does not change state if maxIndex is 0', () async {
      final fakeDb = FakeScriptureDatabase(
        getScriptureCountForPhaseFn: (phase) => 0,
        getScriptureForPhaseByIndexFn: (phase, index) => null,
      );

      container = ProviderContainer(
        overrides: [
          scriptureDatabaseProvider.overrideWithValue(fakeDb),
          userProfileProvider.overrideWith(
            (ref) => UserProfileNotifier(),
          ),
        ],
      );

      final notifier = container.read(scriptureProvider.notifier);
      notifier.initializeScripture(CyclePhase.menstrual);

      final initialState = container.read(scriptureProvider);
      expect(initialState.currentScripture, isNull);
      expect(initialState.maxIndex, 0);

      notifier.getNextScripture();
      notifier.getPreviousScripture();
      notifier.getRandomScripture();

      // State should remain unchanged
      expect(container.read(scriptureProvider), initialState);
    });
  });
}
