import 'dart:convert';
import 'dart:math';
import 'package:http/http.dart' as http;

class MuslimApiException implements Exception {
  final String message;
  const MuslimApiException(this.message);

  @override
  String toString() => 'MuslimApiException: $message';
}

/// Service for muslim.backoffice.biz.id API.
///
/// Exposes Quran, dzikir, doa, hadits, and enrichment data while preserving
/// the data contract currently expected by Quran and dashboard UI widgets.
class MuslimApiService {
  static const String _baseUrl = 'https://muslim.backoffice.biz.id';
  static final MuslimApiService instance = MuslimApiService._();

  MuslimApiService._();

  static const Map<String, String> qariNames = {
    '01': 'Abdullah Al-Juhany',
    '02': 'Abdul Muhsin Al-Qasim',
    '03': 'Abdurrahman As-Sudais',
    '04': 'Ibrahim Al-Dossari',
    '05': 'Misyari Rasyid Al-Afasi',
    '06': 'Yasser Al-Dosari',
  };

  List<Map<String, dynamic>>? _surahListCache;
  final Map<int, Map<String, dynamic>> _surahCache = {};

  List<Map<String, dynamic>>? _allAyahCache;
  List<Map<String, dynamic>>? _tafsirCache;
  List<Map<String, dynamic>>? _asbabCache;
  List<Map<String, dynamic>>? _juzCache;
  List<Map<String, dynamic>>? _themeCache;
  List<Map<String, dynamic>>? _asmaCache;
  List<Map<String, dynamic>>? _doaCache;
  List<Map<String, dynamic>>? _haditsCache;

  final Map<String, List<Map<String, dynamic>>> _dzikirByTypeCache = {};
  final Map<String, List<Map<String, dynamic>>> _wordByWordCache = {};
  final Map<int, List<Map<String, dynamic>>> _pageAyahCache = {};

  Future<dynamic> _getData(String path) async {
    try {
      final response = await http.get(Uri.parse('$_baseUrl$path'));
      if (response.statusCode != 200) {
        return null;
      }
      final decoded = json.decode(response.body);
      if (decoded is Map<String, dynamic>) {
        return decoded['data'];
      }
      return null;
    } catch (_) {
      return null;
    }
  }

  Future<dynamic> _getDataOrThrow(String path) async {
    final response = await http.get(Uri.parse('$_baseUrl$path'));
    if (response.statusCode != 200) {
      throw MuslimApiException(
        'Request failed ($path): HTTP ${response.statusCode}',
      );
    }

    final decoded = json.decode(response.body);
    if (decoded is! Map<String, dynamic>) {
      throw const MuslimApiException('Invalid API payload shape');
    }

    final status = _asInt(decoded['status']);
    if (status != 200) {
      throw MuslimApiException('API returned non-200 status: $status');
    }

    if (!decoded.containsKey('data')) {
      throw const MuslimApiException('API payload missing data key');
    }

    return decoded['data'];
  }

  int _asInt(dynamic value, {int fallback = 0}) {
    if (value is int) return value;
    if (value is num) return value.toInt();
    if (value is String) return int.tryParse(value) ?? fallback;
    return fallback;
  }

  String _asString(dynamic value, {String fallback = ''}) {
    if (value == null) return fallback;
    return value.toString();
  }

  int _asCount(dynamic value, {int fallback = 1}) {
    if (value == null) return fallback;
    if (value is int) return value;
    if (value is num) return value.toInt();
    final text = value.toString();
    final match = RegExp(r'\d+').firstMatch(text);
    if (match == null) return fallback;
    return int.tryParse(match.group(0)!) ?? fallback;
  }

  String _stableDzikirId(String type, Map<String, dynamic> item) {
    final apiId = _asString(item['id']);
    if (apiId.isNotEmpty) {
      return '${type}_$apiId';
    }

    final seed = [
      type,
      _asString(item['type']),
      _asString(item['arab']),
      _asString(item['indo']),
      _asString(item['ulang']),
    ].join('|');

    var hash = 0;
    for (final unit in seed.codeUnits) {
      hash = ((hash * 31) + unit) & 0x7fffffff;
    }
    return '${type}_$hash';
  }

  String _dzikirApiType(String type) {
    switch (type) {
      case 'petang':
        return 'sore';
      default:
        return type;
    }
  }

  Map<String, String> _normalizeAudioMap(dynamic audioValue) {
    final audioUrl = _extractAudioUrl(audioValue);
    if (audioUrl.isEmpty) return {};
    return {
      '01': audioUrl,
      '02': audioUrl,
      '03': audioUrl,
      '04': audioUrl,
      '05': audioUrl,
      '06': audioUrl,
    };
  }

  String _extractAudioUrl(dynamic value) {
    if (value == null) return '';
    if (value is String) return value.trim();
    if (value is Map) {
      final direct = _asString(value['url']).trim();
      if (direct.isNotEmpty) return direct;
      final src = _asString(value['src']).trim();
      if (src.isNotEmpty) return src;
      final audio = _asString(value['audio']).trim();
      if (audio.isNotEmpty) return audio;
    }
    return '';
  }

  String _normalizeQariKey(dynamic rawKey) {
    if (rawKey == null) return '';
    if (rawKey is int) return rawKey.toString().padLeft(2, '0');
    if (rawKey is num) return rawKey.toInt().toString().padLeft(2, '0');

    final text = rawKey.toString().trim();
    if (text.isEmpty) return '';

    final digits = text.replaceAll(RegExp(r'[^0-9]'), '');
    if (digits.isNotEmpty) {
      final parsed = int.tryParse(digits);
      if (parsed != null) return parsed.toString().padLeft(2, '0');
    }
    return text;
  }

  Map<String, String> _normalizeAyahAudioMap(dynamic audioValue) {
    if (audioValue is Map) {
      final normalized = <String, String>{};
      audioValue.forEach((rawKey, rawValue) {
        final key = _normalizeQariKey(rawKey);
        final url = _extractAudioUrl(rawValue);
        if (key.isNotEmpty && url.isNotEmpty) {
          normalized[key] = url;
        }
      });

      if (normalized.isNotEmpty) {
        final fallbackUrl = normalized.values.first;
        for (final qariId in qariNames.keys) {
          normalized.putIfAbsent(qariId, () => fallbackUrl);
        }
        return normalized;
      }
    }

    return _normalizeAudioMap(audioValue);
  }

  Map<String, dynamic> _mapSurahSummary(Map<String, dynamic> item) {
    final number = _asInt(item['number']);
    return {
      'nomor': number,
      'nama': _asString(item['name_short']),
      'namaLatin': _asString(item['name_id']),
      'jumlahAyat': _asInt(item['number_of_verses']),
      'tempatTurun': _asString(item['revelation_id']),
      'arti': _asString(item['translation_id']),
      'deskripsi': _asString(item['tafsir']),
      'audioFull': _normalizeAudioMap(item['audio_url']),
    };
  }

  Map<String, dynamic> _mapAyah(Map<String, dynamic> item) {
    return {
      'nomorAyat': _asInt(item['ayah']),
      'teksArab': _asString(item['arab']),
      'teksLatin': _asString(item['latin']),
      'teksIndonesia': _asString(item['text']),
      'audio': _normalizeAyahAudioMap(item['audio'] ?? item['audio_url']),
      'juz': _asInt(item['juz']),
      'page': _asInt(item['page']),
      'hizb': _asInt(item['hizb']),
      'theme': _asString(item['theme']),
      'asbab': _asString(item['asbab']),
      'notes': _asString(item['notes']),
      'surah': _asInt(item['surah']),
      'ayahId': _asInt(item['id']),
    };
  }

  Future<List<Map<String, dynamic>>> getAllSurahs() async {
    if (_surahListCache != null) return _surahListCache!;
    final raw = await _getData('/v1/quran/surah');
    if (raw is! List) return [];
    _surahListCache =
        raw.whereType<Map<String, dynamic>>().map(_mapSurahSummary).toList();
    return _surahListCache!;
  }

  Future<Map<String, dynamic>?> getSurah(int number) async {
    if (_surahCache.containsKey(number)) {
      return _surahCache[number];
    }

    final surahs = await getAllSurahs();
    Map<String, dynamic>? summary;
    for (final surah in surahs) {
      if (surah['nomor'] == number) {
        summary = surah;
        break;
      }
    }
    final rawAyah = await _getData('/v1/quran/ayah/surah?id=$number');
    if (summary == null || rawAyah is! List) {
      return null;
    }

    final mappedAyah =
        rawAyah.whereType<Map<String, dynamic>>().map(_mapAyah).toList();

    final mapped = {
      ...summary,
      'ayat': mappedAyah,
    };
    _surahCache[number] = mapped;
    return mapped;
  }

  Future<Map<String, dynamic>?> getDailyAyat() async {
    try {
      final now = DateTime.now();
      final dayOfYear = now.difference(DateTime(now.year, 1, 1)).inDays;
      final surahId = (dayOfYear % 114) + 1;
      final surah = await getSurah(surahId);
      if (surah == null) return null;

      final ayat = List<Map<String, dynamic>>.from(surah['ayat'] ?? []);
      if (ayat.isEmpty) return null;

      final ayatIndex = dayOfYear % ayat.length;
      final picked = ayat[ayatIndex];
      return {
        'surahName': surah['namaLatin'] ?? '',
        'nomorSurah': surahId,
        'nomorAyat': picked['nomorAyat'] ?? 1,
        'teksArab': picked['teksArab'] ?? '',
        'teksIndonesia': picked['teksIndonesia'] ?? '',
      };
    } catch (_) {
      return null;
    }
  }

  Future<Map<String, dynamic>?> getRandomAyat({
    int? excludeSurahNumber,
    int? excludeAyahNumber,
  }) async {
    try {
      final allAyah = await getAllAyah();
      if (allAyah.isEmpty) return null;

      final surahs = await getAllSurahs();
      if (surahs.isEmpty) return null;

      final surahNames = <int, String>{
        for (final surah in surahs)
          _asInt(surah['nomor']): _asString(surah['namaLatin']),
      };

      final filtered = allAyah.where((ayah) {
        final surahNumber = _asInt(ayah['surah']);
        final ayahNumber = _asInt(ayah['ayah']);
        final isExcluded = excludeSurahNumber != null &&
            excludeAyahNumber != null &&
            surahNumber == excludeSurahNumber &&
            ayahNumber == excludeAyahNumber;
        if (isExcluded) return false;

        return _asString(ayah['arab']).trim().isNotEmpty &&
            _asString(ayah['text']).trim().isNotEmpty;
      }).toList();

      final candidates = filtered.isNotEmpty ? filtered : allAyah;
      final picked = candidates[Random().nextInt(candidates.length)];
      final surahNumber = _asInt(picked['surah']);

      return {
        'surahName': surahNames[surahNumber] ?? '',
        'nomorSurah': surahNumber,
        'nomorAyat': _asInt(picked['ayah'], fallback: 1),
        'teksArab': _asString(picked['arab']),
        'teksIndonesia': _asString(picked['text']),
      };
    } catch (_) {
      return null;
    }
  }

  Future<List<Map<String, dynamic>>> getWordByWord(
      int surahId, int ayahId) async {
    final key = '$surahId:$ayahId';
    if (_wordByWordCache.containsKey(key)) return _wordByWordCache[key]!;

    final raw =
        await _getData('/v1/quran/word/ayah?surahId=$surahId&ayahId=$ayahId');
    if (raw is! List) return [];

    final mapped = raw.whereType<Map<String, dynamic>>().map((item) {
      return {
        'word': _asString(item['word']),
        'arab': _asString(item['arab']),
        'indo': _asString(item['indo']),
      };
    }).toList();

    _wordByWordCache[key] = mapped;
    return mapped;
  }

  Future<List<Map<String, dynamic>>> getAllAyah() async {
    if (_allAyahCache != null) return _allAyahCache!;
    final raw = await _getData('/v1/quran/ayah');
    if (raw is! List) return [];

    _allAyahCache = raw.whereType<Map<String, dynamic>>().map((item) {
      return {
        'id': _asInt(item['id']),
        'surah': _asInt(item['surah']),
        'ayah': _asInt(item['ayah']),
        'arab': _asString(item['arab']),
        'latin': _asString(item['latin']),
        'text': _asString(item['text']),
        'juz': _asInt(item['juz']),
        'page': _asInt(item['page']),
        'hizb': _asInt(item['hizb']),
        'theme': _asString(item['theme']),
        'asbab': _asString(item['asbab']),
      };
    }).toList();

    return _allAyahCache!;
  }

  Future<List<Map<String, dynamic>>> getTafsirBySurah(int surahId) async {
    if (_tafsirCache == null) {
      final raw = await _getData('/v1/quran/tafsir');
      if (raw is! List) return [];
      _tafsirCache = raw.whereType<Map<String, dynamic>>().map((item) {
        return {
          'id': _asInt(item['id']),
          'ayah': _asInt(item['ayah']),
          'wajiz': _asString(item['wajiz']),
          'tahlili': _asString(item['tahlili']),
        };
      }).toList();
    }

    final allAyah = await getAllAyah();
    if (allAyah.isEmpty || _tafsirCache == null) return [];

    final ayahById = <int, Map<String, dynamic>>{};
    final ayahBySurahAyah = <String, Map<String, dynamic>>{};
    for (final ayah in allAyah) {
      final id = _asInt(ayah['id']);
      final surah = _asInt(ayah['surah']);
      final ayahNumber = _asInt(ayah['ayah']);
      ayahById[id] = ayah;
      ayahBySurahAyah['$surah:$ayahNumber'] = ayah;
    }

    final result = <Map<String, dynamic>>[];
    for (final tafsir in _tafsirCache!) {
      final tafsirId = _asInt(tafsir['id']);
      final tafsirAyah = _asInt(tafsir['ayah']);
      Map<String, dynamic>? ayahMeta = ayahById[tafsirId];
      ayahMeta ??= ayahBySurahAyah['$surahId:$tafsirAyah'];
      if (ayahMeta == null) continue;
      if (ayahMeta['surah'] != surahId) continue;
      result.add({
        'nomorAyat': _asInt(ayahMeta['ayah'], fallback: tafsirAyah),
        'wajiz': tafsir['wajiz'],
        'tahlili': tafsir['tahlili'],
      });
    }

    result.sort(
        (a, b) => (a['nomorAyat'] as int).compareTo(b['nomorAyat'] as int));
    return result;
  }

  Future<List<Map<String, dynamic>>> getAsbabBySurah(int surahId) async {
    if (_asbabCache == null) {
      final raw = await _getData('/v1/quran/asbab');
      if (raw is! List) return [];
      _asbabCache = raw.whereType<Map<String, dynamic>>().map((item) {
        return {
          'id': _asInt(item['id']),
          'ayah': _asInt(item['ayah']),
          'text': _asString(item['text']),
        };
      }).toList();
    }

    final allAyah = await getAllAyah();
    if (allAyah.isEmpty || _asbabCache == null) return [];

    final ayahById = <int, Map<String, dynamic>>{};
    final ayahBySurahAyah = <String, Map<String, dynamic>>{};
    for (final ayah in allAyah) {
      final id = _asInt(ayah['id']);
      final surah = _asInt(ayah['surah']);
      final ayahNumber = _asInt(ayah['ayah']);
      ayahById[id] = ayah;
      ayahBySurahAyah['$surah:$ayahNumber'] = ayah;
    }

    final result = <Map<String, dynamic>>[];
    for (final asbab in _asbabCache!) {
      final asbabId = _asInt(asbab['id']);
      final asbabAyah = _asInt(asbab['ayah']);
      Map<String, dynamic>? ayahMeta = ayahById[asbabId];
      ayahMeta ??= ayahBySurahAyah['$surahId:$asbabAyah'];
      if (ayahMeta == null) continue;
      if (ayahMeta['surah'] != surahId) continue;
      result.add({
        'nomorAyat': _asInt(ayahMeta['ayah'], fallback: asbabAyah),
        'text': asbab['text'],
      });
    }

    result.sort(
        (a, b) => (a['nomorAyat'] as int).compareTo(b['nomorAyat'] as int));
    return result;
  }

  Future<List<Map<String, dynamic>>> getJuzList() async {
    if (_juzCache != null) return _juzCache!;
    final raw = await _getData('/v1/quran/juz');
    if (raw is! List) return [];

    _juzCache = raw.whereType<Map<String, dynamic>>().map((item) {
      return {
        'number': _asInt(item['number']),
        'name': _asString(item['name']),
        'surah_id_start': _asInt(item['surah_id_start']),
        'verse_start': _asInt(item['verse_start']),
        'surah_id_end': _asInt(item['surah_id_end']),
        'verse_end': _asInt(item['verse_end']),
        'name_start_id': _asString(item['name_start_id']),
        'name_end_id': _asString(item['name_end_id']),
      };
    }).toList();

    return _juzCache!;
  }

  Future<List<Map<String, dynamic>>> getAyahByPage(int page) async {
    if (_pageAyahCache.containsKey(page)) return _pageAyahCache[page]!;
    final raw = await _getData('/v1/quran/ayah/page?id=$page');
    if (raw is! List) return [];

    final mapped = raw.whereType<Map<String, dynamic>>().map((item) {
      return {
        'surah': _asInt(item['surah']),
        'ayah': _asInt(item['ayah']),
        'arab': _asString(item['arab']),
        'text': _asString(item['text']),
        'theme': _asString(item['theme']),
      };
    }).toList();

    _pageAyahCache[page] = mapped;
    return mapped;
  }

  Future<List<Map<String, dynamic>>> getThemes() async {
    if (_themeCache != null) return _themeCache!;
    final raw = await _getData('/v1/quran/theme');
    if (raw is! List) return [];

    _themeCache = raw.whereType<Map<String, dynamic>>().map((item) {
      return {
        'id': _asInt(item['id']),
        'name': _asString(item['name']),
      };
    }).toList();
    return _themeCache!;
  }

  Future<List<Map<String, dynamic>>> searchAyah(String query) async {
    final q = query.trim().toLowerCase();
    if (q.isEmpty) return [];

    final allAyah = await getAllAyah();
    final results = allAyah
        .where((item) {
          final text = _asString(item['text']).toLowerCase();
          final latin = _asString(item['latin']).toLowerCase();
          final arab = _asString(item['arab']);
          return text.contains(q) ||
              latin.contains(q) ||
              arab.contains(query.trim());
        })
        .take(50)
        .toList();

    return results;
  }

  Future<List<Map<String, dynamic>>> getAsmaulHusna() async {
    if (_asmaCache != null) return _asmaCache!;
    final raw = await _getData('/v1/quran/asma');
    if (raw is! List) return [];

    _asmaCache = raw.whereType<Map<String, dynamic>>().map((item) {
      return {
        'id': _asInt(item['id']),
        'arab': _asString(item['arab']),
        'latin': _asString(item['latin']),
        'indo': _asString(item['indo']),
      };
    }).toList();

    return _asmaCache!;
  }

  Future<List<Map<String, dynamic>>> getDoaList({bool strict = false}) async {
    if (_doaCache != null) return _doaCache!;
    final raw =
        strict ? await _getDataOrThrow('/v1/doa') : await _getData('/v1/doa');
    if (raw is! List) {
      if (strict) {
        throw const MuslimApiException('Invalid doa payload');
      }
      return [];
    }

    _doaCache = raw.whereType<Map<String, dynamic>>().map((item) {
      return {
        'judul': _asString(item['judul']),
        'arab': _asString(item['arab']),
        'indo': _asString(item['indo']),
        'source': _asString(item['source']),
      };
    }).toList();

    return _doaCache!;
  }

  Future<List<Map<String, dynamic>>> getHaditsList(
      {bool strict = false}) async {
    if (_haditsCache != null) return _haditsCache!;
    final raw = strict
        ? await _getDataOrThrow('/v1/hadits')
        : await _getData('/v1/hadits');
    if (raw is! List) {
      if (strict) {
        throw const MuslimApiException('Invalid hadits payload');
      }
      return [];
    }

    _haditsCache = raw.whereType<Map<String, dynamic>>().map((item) {
      return {
        'no': _asInt(item['no']),
        'judul': _asString(item['judul']),
        'arab': _asString(item['arab']),
        'indo': _asString(item['indo']),
      };
    }).toList();

    return _haditsCache!;
  }

  Future<List<Map<String, dynamic>>> getDzikirByType(
    String type, {
    bool strict = false,
  }) async {
    if (_dzikirByTypeCache.containsKey(type)) {
      return _dzikirByTypeCache[type]!;
    }
    final apiType = _dzikirApiType(type);
    final raw = strict
        ? await _getDataOrThrow('/v1/dzikir?type=$apiType')
        : await _getData('/v1/dzikir?type=$apiType');
    if (raw is! List) {
      if (strict) {
        throw MuslimApiException('Invalid dzikir payload for type: $type');
      }
      return [];
    }

    final mapped = <Map<String, dynamic>>[];
    for (var i = 0; i < raw.length; i++) {
      final item = raw[i];
      if (item is! Map<String, dynamic>) continue;
      mapped.add({
        'id': _stableDzikirId(type, item),
        'arab': _asString(item['arab']),
        'indo': _asString(item['indo']),
        'type': _asString(item['type']),
        'ulang': _asCount(item['ulang'], fallback: 1),
      });
    }

    _dzikirByTypeCache[type] = mapped;
    return mapped;
  }
}
