import 'dart:convert';
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:package_info_plus/package_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';

// ─── Configuration ────────────────────────────────────────────────────────────

const _kGiteaBase = 'https://git.steggi-matrix.work';
const _kGiteaRepo = 'steggi/pyramid';
const _kCheckIntervalHours = 4;
const _kPrefLastCheck = 'update_last_check_ms';
const _kPrefSkippedTag = 'update_skipped_tag';

// ─── Data model ───────────────────────────────────────────────────────────────

class UpdateInfo {
  final String tagName;
  final String releaseName;
  final String body;
  final String htmlUrl;
  final String? downloadUrl;
  final (int, int, int) remoteVersion;

  const UpdateInfo({
    required this.tagName,
    required this.releaseName,
    required this.body,
    required this.htmlUrl,
    required this.remoteVersion,
    this.downloadUrl,
  });

  bool get canDownload => downloadUrl != null;
}

// ─── Version helpers ──────────────────────────────────────────────────────────

(int, int, int)? _parseVersion(String raw) {
  final clean = raw.trim().replaceFirst(RegExp(r'^v'), '').split('+').first;
  final parts = clean.split('.');
  if (parts.length < 3) return null;
  try {
    return (int.parse(parts[0]), int.parse(parts[1]), int.parse(parts[2]));
  } catch (_) {
    return null;
  }
}

bool _isNewer((int, int, int) remote, (int, int, int) local) {
  if (remote.$1 != local.$1) return remote.$1 > local.$1;
  if (remote.$2 != local.$2) return remote.$2 > local.$2;
  return remote.$3 > local.$3;
}

// Rewrite the host in a Gitea asset URL to always use the configured base,
// so downloads work from outside the home network once nginx is set up.
String? _rewriteHost(String? url) {
  if (url == null) return null;
  return url.replaceFirst(RegExp(r'https?://[^/]+'), _kGiteaBase);
}

// Device ABIs in preference order (e.g. [arm64-v8a, armeabi-v7a, armeabi]),
// fetched once from the platform. Used to pick the matching split APK.
Future<List<String>> _deviceAbis() async {
  if (!Platform.isAndroid) return const [];
  try {
    final abis = await const MethodChannel('chat.pyramid.pyramid/install')
        .invokeListMethod<String>('getAbis');
    return abis?.map((a) => a.toLowerCase()).toList() ?? const [];
  } catch (_) {
    return const [];
  }
}

String? _findDownloadUrl(List<dynamic> assets, List<String> abis) {
  if (assets.isEmpty) return null;
  final list = assets.cast<Map<String, dynamic>>();

  String? pick(bool Function(String name) test) {
    for (final a in list) {
      final name = (a['name'] as String? ?? '').toLowerCase();
      if (test(name)) return a['browser_download_url'] as String?;
    }
    return null;
  }

  if (Platform.isWindows) {
    return pick((n) => n.endsWith('.exe')) ?? pick((n) => n.endsWith('.msix'));
  }
  if (Platform.isAndroid) {
    // Prefer the split APK matching the device's best ABI, then weaker ABIs.
    // Fall back to any APK so fat-APK releases (pre-split) keep working.
    for (final abi in abis) {
      final url = pick((n) => n.endsWith('.apk') && n.contains(abi));
      if (url != null) return url;
    }
    return pick((n) => n.endsWith('.apk'));
  }
  return null;
}

// ─── Fetch ────────────────────────────────────────────────────────────────────

Future<UpdateInfo?> _fetchLatestRelease(List<String> abis) async {
  // Fetch enough releases to find the highest version even if they were
  // created out of chronological order (Gitea sorts by created_at, not semver).
  final uri = Uri.parse(
    '$_kGiteaBase/api/v1/repos/$_kGiteaRepo/releases?limit=20',
  );
  // Let network/timeout exceptions propagate so the provider shows an error
  // state instead of silently appearing as "up to date".
  final response = await http
      .get(uri, headers: {'Accept': 'application/json'})
      .timeout(const Duration(seconds: 10));

  if (response.statusCode != 200) {
    throw Exception('Gitea antwortete mit ${response.statusCode}');
  }

  try {
    final list = json.decode(response.body) as List<dynamic>;
    if (list.isEmpty) return null;

    // Find the release with the highest version number, ignoring drafts and
    // pre-releases. This is robust even when releases are created out of order.
    UpdateInfo? best;
    for (final item in list) {
      final data = item as Map<String, dynamic>;
      if (data['draft'] == true || data['prerelease'] == true) continue;

      final tagName = data['tag_name'] as String? ?? '';
      if (tagName.isEmpty) continue;

      final remoteVer = _parseVersion(tagName);
      if (remoteVer == null) continue;

      if (best == null || _isNewer(remoteVer, best.remoteVersion)) {
        final assets = (data['assets'] as List<dynamic>?) ?? [];
        final downloadUrl = _rewriteHost(_findDownloadUrl(assets, abis));
        best = UpdateInfo(
          tagName: tagName,
          releaseName: data['name'] as String? ?? tagName,
          body: data['body'] as String? ?? '',
          htmlUrl: '$_kGiteaBase/$_kGiteaRepo/releases/tag/$tagName',
          remoteVersion: remoteVer,
          downloadUrl: downloadUrl,
        );
      }
    }
    return best;
  } catch (_) {
    return null;
  }
}

// ─── Provider ─────────────────────────────────────────────────────────────────

final updateInfoProvider = FutureProvider<UpdateInfo?>((ref) async {
  // iOS: can't sideload, skip
  if (Platform.isIOS || Platform.isMacOS || Platform.isLinux) return null;

  final prefs = await SharedPreferences.getInstance();
  final lastCheckMs = prefs.getInt(_kPrefLastCheck) ?? 0;
  final elapsed = DateTime.now().millisecondsSinceEpoch - lastCheckMs;
  if (elapsed < _kCheckIntervalHours * 3600 * 1000) return null;

  await prefs.setInt(_kPrefLastCheck, DateTime.now().millisecondsSinceEpoch);

  final remote = await _fetchLatestRelease(await _deviceAbis());
  if (remote == null) return null;

  final info = await PackageInfo.fromPlatform();
  final localVer = _parseVersion(info.version);
  if (localVer == null) return null;

  if (!_isNewer(remote.remoteVersion, localVer)) return null;

  final skipped = prefs.getString(_kPrefSkippedTag);
  if (skipped == remote.tagName) return null;

  return remote;
});

// ─── Actions ──────────────────────────────────────────────────────────────────

Future<void> skipUpdateVersion(String tagName) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString(_kPrefSkippedTag, tagName);
}

Future<void> resetUpdateCheckTimer() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.remove(_kPrefLastCheck);
}
