// The multi-server registry: which server the app reads from, and the session
// held for each.
//
// What is worth guarding is that the servers stay separate — one server's
// session is never handed to another, and dropping one never takes the rest
// with it — and that an upgrade from the single-server build carries the
// session and the address it was pointed at onto the home entry rather than
// signing the owner out.
import "dart:convert";

import "package:flutter_test/flutter_test.dart";
import "package:shared_preferences/shared_preferences.dart";

import "package:drivervault_phone/config.dart";
import "package:drivervault_phone/i18n.dart";
import "package:drivervault_phone/models.dart";
import "package:drivervault_phone/servers.dart";

AuthUser _user(String email) => AuthUser(id: "u1", email: email, name: "Owner");

Future<ServerRegistry> _loaded(Map<String, Object> stored) async {
  SharedPreferences.setMockInitialValues(stored);
  final registry = ServerRegistry();
  await registry.load();
  return registry;
}

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  setUpAll(() async {
    await loadTranslations();
  });

  group("normalizeServerUrl", () {
    test("a bare origin gets the /api every route lives under", () {
      expect(normalizeServerUrl("https://garage.example.com"), "https://garage.example.com/api");
      expect(normalizeServerUrl("https://garage.example.com/"), "https://garage.example.com/api");
    });

    test("an address that already names a path is taken as given", () {
      expect(normalizeServerUrl("https://example.com/api"), "https://example.com/api");
      expect(normalizeServerUrl("https://example.com/dv/api/"), "https://example.com/dv/api");
    });

    test("blank stays blank — that is what means 'the default'", () {
      expect(normalizeServerUrl("  "), "");
    });
  });

  group("a fresh install", () {
    test("knows one server, unnamed, answering on the built-in address", () async {
      final registry = await _loaded({});
      expect(registry.list.length, 1);
      expect(registry.activeId, kHomeServerId);
      expect(registry.activeBase, kDefaultApiBase);
      expect(registry.isConnected(kHomeServerId), isFalse);
      expect(registry.displayName(registry.active), t("servers.home"));
    });
  });

  group("upgrading from the single-server build", () {
    test("the session and the address it was pointed at land on home", () async {
      final registry = await _loaded({
        "cc_token": "old-token",
        "cc_user": jsonEncode(_user("owner@example.com").toJson()),
        "cc_server_url": "https://garage.example.com",
      });

      expect(registry.activeId, kHomeServerId);
      expect(registry.activeToken, "old-token");
      expect(registry.activeSession?.user?.email, "owner@example.com");
      expect(registry.activeBase, "https://garage.example.com/api");

      // The keys they came from are gone, so a later boot cannot resurrect a
      // session that has since been signed out of.
      final prefs = await SharedPreferences.getInstance();
      expect(prefs.getString("cc_token"), isNull);
      expect(prefs.getString("cc_server_url"), isNull);
    });
  });

  group("several servers", () {
    test("a session belongs to the one server it was minted by", () async {
      final registry = await _loaded({});
      registry.setSession(kHomeServerId, "home-token", _user("home@example.com"));
      final other = registry.add(name: "Work", url: "https://work.example.com");

      expect(registry.isConnected(other.id), isFalse);
      // Switching swaps the whole session, not just the address.
      registry.setActive(other.id);
      expect(registry.activeBase, "https://work.example.com/api");
      expect(registry.activeToken, isNull);

      registry.setSession(other.id, "work-token", _user("work@example.com"));
      expect(registry.activeToken, "work-token");
      expect(registry.sessionFor(kHomeServerId)?.token, "home-token");
    });

    test("moving one to a new address drops the token the old one minted", () async {
      final registry = await _loaded({});
      final server = registry.add(url: "https://work.example.com");
      registry.setSession(server.id, "work-token", _user("work@example.com"));

      registry.update(server.id, name: "Work");
      expect(registry.isConnected(server.id), isTrue, reason: "a rename is not a move");

      registry.update(server.id, url: "https://elsewhere.example.com");
      expect(registry.isConnected(server.id), isFalse);
    });

    test("removing the active one falls back to home, and leaves it signed in", () async {
      final registry = await _loaded({});
      registry.setSession(kHomeServerId, "home-token", _user("home@example.com"));
      final other = registry.add(url: "https://work.example.com");
      registry.setSession(other.id, "work-token", _user("work@example.com"));
      registry.setActive(other.id);

      registry.remove(other.id);
      expect(registry.activeId, kHomeServerId);
      expect(registry.activeToken, "home-token");
      expect(registry.list.length, 1);
    });

    test("home cannot be removed — there would be nothing to fall back to", () async {
      final registry = await _loaded({});
      registry.remove(kHomeServerId);
      expect(registry.list.length, 1);
      expect(registry.byId(kHomeServerId), isNotNull);
    });

    test("what they are called: the name, else the host, else the default label", () async {
      final registry = await _loaded({});
      expect(registry.displayName(registry.add(name: "Home garage", url: "https://a.example.com")),
          "Home garage");
      expect(registry.displayName(registry.add(url: "https://b.example.com")), "b.example.com");
      expect(registry.displayName(registry.byId(kHomeServerId)), t("servers.home"));
    });
  });

  group("what a later boot reads back", () {
    test("the list, the active one and every session survive a restart", () async {
      final registry = await _loaded({});
      registry.setSession(kHomeServerId, "home-token", _user("home@example.com"));
      final other = registry.add(name: "Work", url: "https://work.example.com");
      registry.setSession(other.id, "work-token", _user("work@example.com"));
      registry.setActive(other.id);

      final rebooted = ServerRegistry();
      await rebooted.load();

      expect(rebooted.list.length, 2);
      expect(rebooted.activeId, other.id);
      expect(rebooted.activeToken, "work-token");
      expect(rebooted.activeSession?.user?.email, "work@example.com");
      expect(rebooted.displayName(rebooted.active), "Work");
      expect(rebooted.sessionFor(kHomeServerId)?.token, "home-token");
    });
  });
}
