import 'dart:async';
import 'dart:io';
import 'package:flutter/widgets.dart';
import 'package:shared_preferences/shared_preferences.dart';

const spOrigin = "redirect_cache_origin";
const spResolved = "redirect_cache_resolved";

Future<void> sleep(int ms) async {
  await Future.delayed(Duration(milliseconds: ms));
}

class HeadResult {
  final int statusCode;
  final bool redirected;
  final String? location;

  HeadResult(this.statusCode, this.redirected, this.location);
}

Future<HeadResult?> headRequest(String url) async {
  try {
    final client = HttpClient();
    final request = await client.openUrl("HEAD", Uri.parse(url));

    request.followRedirects = false;
    request.headers.set("Cache-Control", "no-cache");
    request.headers.set("Pragma", "no-cache");

    final response = await request.close();

    final location = response.headers.value(HttpHeaders.locationHeader);

    final redirected = response.statusCode >= 300 && response.statusCode < 400;

    return HeadResult(response.statusCode, redirected, location);
  } catch (e) {
    return null;
  }
}

Future<bool> canConnect(String url) async {
  try {
    final res = await headRequest(url);
    return res != null;
  } catch (_) {
    return false;
  }
}

Future<String> getRedirect(String url) async {
  try {
    final res = await headRequest(url);

    if (res == null) {
      return url;
    }

    debugPrint(
      "[RedirectResolver] head $url "
      "status=${res.statusCode} "
      "redirected=${res.redirected} "
      "location=${res.location}",
    );

    if (res.redirected && res.location != null) {
      try {
        return Uri.parse(url).resolve(res.location!).toString();
      } catch (_) {
        return res.location!;
      }
    }

    return url;
  } catch (_) {
    return url;
  }
}

Future<String?> resolveWithRetry(String originUrl) async {
  for (int i = 0; i < 3; i++) {
    final redirect = await getRedirect(originUrl);

    if (redirect == originUrl) {
      return originUrl;
    }

    if (await canConnect(redirect)) {
      return redirect;
    }

    if (i < 2) {
      await sleep(100);
    }
  }

  return null;
}

Future<String> resolveRedirectUrl(String originUrl) async {
  try {
    final prefs = await SharedPreferences.getInstance();

    final cacheOrigin = prefs.getString(spOrigin);
    final cacheResolved = prefs.getString(spResolved);

    if (originUrl == cacheOrigin && cacheResolved != null) {
      if (await canConnect(cacheResolved)) {
        return cacheResolved;
      }
    }

    final resolved = await resolveWithRetry(originUrl);

    if (resolved != null) {
      await prefs.setString(spOrigin, originUrl);
      await prefs.setString(spResolved, resolved);
      return resolved;
    }

    return originUrl;
  } catch (_) {
    return originUrl;
  }
}
