import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals";
import { flushPromises, mount } from "@vue/test-utils";
import { defineComponent, nextTick } from "vue";

import { auth } from "../../../frontend/src/auth.js";
import { useAccountDashboard } from "../../../frontend/src/account/useAccountDashboard.js";
import AccountView from "../../../frontend/src/views/AccountView.vue";

function jsonResponse(body, status = 200, statusText = "OK") {
  return new Response(JSON.stringify(body), {
    status,
    statusText,
    headers: { "content-type": "application/json" },
  });
}

function overview(overrides = {}) {
  return {
    mailu: { status: "ready", username: "ADA@BSTEIN.DEV", app_password: "mail-pass" },
    nextcloud_mail: { status: "synced", primary_email: "ADA@BSTEIN.DEV", account_count: 1, synced_at: "now" },
    wger: { status: "ready", username: "ADA@BSTEIN.DEV", password: "wger-pass", password_updated_at: "today" },
    firefly: { status: "ready", username: "ADA@BSTEIN.DEV", password: "firefly-pass", password_updated_at: "today" },
    vaultwarden: { status: "already_present", username: "ADA@BSTEIN.DEV", synced_at: "now" },
    jellyfin: { status: "ready", username: "ada", sync_status: "ok", sync_detail: "LDAP synced" },
    onboarding_url: "/onboarding?code=ada",
    ...overrides,
  };
}

function installFetch(handler) {
  global.fetch = jest.fn(async (url, options = {}) => handler(String(url), options));
}

function authenticate() {
  auth.ready = true;
  auth.enabled = true;
  auth.authenticated = true;
  auth.username = "ada";
  auth.email = "Ada@Example.Dev";
  auth.token = "token";
  auth.accountPasswordUrl = "https://sso.example.dev/account/password";
}

function mountDashboard() {
  let dashboard;
  const wrapper = mount(defineComponent({
    setup() {
      dashboard = useAccountDashboard();
      return {};
    },
    template: "<div />",
  }));
  return { dashboard, wrapper };
}

describe("account dashboard", () => {
  beforeEach(() => {
    jest.useFakeTimers();
    authenticate();
    Object.defineProperty(navigator, "clipboard", {
      configurable: true,
      value: { writeText: jest.fn(async () => {}) },
    });
    installFetch((url) => {
      if (url.includes("/api/account/overview")) return jsonResponse(overview());
      if (url.includes("/api/admin/access/requests")) {
        return jsonResponse({
          requests: [{
            username: "newuser",
            first_name: "New",
            last_name: "User",
            email: "new@example.dev",
            request_code: "newuser~abc",
            note: "please",
          }],
        });
      }
      if (url.includes("/api/admin/access/flags")) return jsonResponse({ flags: ["media", "budget"] });
      return jsonResponse({});
    });
  });

  afterEach(() => {
    jest.useRealTimers();
    jest.restoreAllMocks();
    Object.assign(auth, {
      ready: false,
      enabled: false,
      authenticated: false,
      username: "",
      email: "",
      token: "",
      accountPasswordUrl: "",
    });
    Reflect.deleteProperty(global, "fetch");
  });

  it("renders login-required state when unauthenticated", async () => {
    auth.ready = true;
    auth.authenticated = false;
    const { dashboard, wrapper } = mountDashboard();
    await nextTick();

    expect(dashboard.mailu.status).toBe("login required");
    expect(dashboard.vaultwardenDisplayStatus.value).toBe("login required");
    expect(dashboard.wolf.status).toBe("login required");
    expect(dashboard.admin.enabled).toBe(false);
    wrapper.unmount();

    const view = mount(AccountView);
    await nextTick();
    expect(view.text()).toContain("Login required");
    view.unmount();
  });

  it("reacts when auth readiness changes after mount", async () => {
    auth.ready = false;
    auth.authenticated = false;
    const { dashboard, wrapper } = mountDashboard();
    await nextTick();

    auth.ready = true;
    await nextTick();
    expect(dashboard.mailu.status).toBe("login required");
    expect(dashboard.admin.requests).toEqual([]);

    auth.authenticated = true;
    auth.username = "ada";
    auth.email = "ada@example.dev";
    auth.token = "token";
    await flushPromises();
    expect(dashboard.mailu.username).toBe("ada@bstein.dev");
    expect(dashboard.admin.enabled).toBe(true);
    wrapper.unmount();
  });

  it("loads overview, admin requests, and renders the authenticated view", async () => {
    const { dashboard, wrapper } = mountDashboard();
    await flushPromises();

    expect(dashboard.mailu.username).toBe("ada@bstein.dev");
    expect(dashboard.mailu.currentPassword).toBe("mail-pass");
    expect(dashboard.wger.password).toBe("wger-pass");
    expect(dashboard.firefly.password).toBe("firefly-pass");
    expect(dashboard.vaultwardenReady.value).toBe(true);
    expect(dashboard.vaultwardenOrder.value).toBe(3);
    expect(dashboard.jellyfin.syncStatus).toBe("ok");
    expect(dashboard.wolf.status).toBe("degraded");
    expect(dashboard.onboardingUrl.value).toBe("/onboarding?code=ada");
    expect(dashboard.admin.enabled).toBe(true);
    expect(dashboard.admin.flags).toEqual(["media", "budget"]);
    expect(dashboard.hasFlag("newuser", "media")).toBe(false);
    expect(dashboard.formatName(dashboard.admin.requests[0])).toBe("New User");
    expect(dashboard.formatName({ first_name: "  ", last_name: "" })).toBe("unknown");
    expect(dashboard.formatName()).toBe("unknown");

    dashboard.toggleFlag("newuser", "media", { target: { checked: true } });
    dashboard.toggleFlag("newuser", "budget", { target: { checked: true } });
    dashboard.toggleFlag("newuser", "media", { target: { checked: false } });
    expect(dashboard.admin.selectedFlags.newuser).toEqual(["budget"]);
    wrapper.unmount();

    const view = mount(AccountView);
    await flushPromises();
    expect(view.text()).toContain("Firefly III");
    expect(view.text()).toContain("Wolf");
    expect(view.text()).toContain("Admin Approvals");
    expect(view.text()).toContain("newuser");
    expect(view.find("a[href='https://sso.example.dev/account/password']").exists()).toBe(true);
    view.unmount();
  });

  it("rotates service credentials and syncs account services", async () => {
    installFetch((url) => {
      if (url.includes("/mailu/rotate")) return jsonResponse({ password: "new-mail", sync_enabled: true, sync_ok: true });
      if (url.includes("/wger/reset")) return jsonResponse({ password: "new-wger" });
      if (url.includes("/firefly/reset")) return jsonResponse({ password: "new-firefly" });
      if (url.includes("/nextcloud/mail/sync")) return jsonResponse({ ok: true });
      if (url.includes("/api/account/overview")) return jsonResponse(overview());
      if (url.includes("/api/admin/access/requests")) return jsonResponse({ requests: [] });
      if (url.includes("/api/admin/access/flags")) return jsonResponse({ flags: [] });
      return jsonResponse({});
    });
    const { dashboard, wrapper } = mountDashboard();
    await flushPromises();

    await dashboard.rotateMailu();
    expect(dashboard.mailu.currentPassword).toBe("mail-pass");
    expect(dashboard.mailu.rotating).toBe(false);

    await dashboard.resetWger();
    expect(dashboard.wger.password).toBe("wger-pass");
    expect(dashboard.wger.resetting).toBe(false);

    await dashboard.resetFirefly();
    expect(dashboard.firefly.password).toBe("firefly-pass");
    expect(dashboard.firefly.resetting).toBe(false);

    await dashboard.syncNextcloudMail();
    expect(dashboard.nextcloudMail.syncing).toBe(false);
    wrapper.unmount();
  });

  it("loads and controls Wolf account state", async () => {
    const seen = [];
    installFetch((url, options = {}) => {
      seen.push({ url, body: options.body || "" });
      if (url.includes("api.ipify.org")) return jsonResponse({ ip: "181.1.87.186" });
      if (url.includes("/api/account/wolf/firewall/unlock")) return jsonResponse({ success: true });
      if (url.includes("/api/account/wolf/pairing/submit-pin")) return jsonResponse({ success: true });
      if (url.includes("/api/account/wolf/game-mode/start")) return jsonResponse({ status: "active" });
      if (url.includes("/api/account/wolf/game-mode/stop")) return jsonResponse({ status: "idle" });
      if (url.includes("/api/account/wolf/admin/firewall/unlock")) return jsonResponse({ success: true });
      if (url.includes("/api/account/wolf")) {
        return jsonResponse({
          can_control_gpu: true,
          moonlight: { host: "moonlight.bstein.dev", source_ip: "181.1.87.186", unlock_ttl_seconds: 28800 },
          gpu: {
            priority: "ai",
            game_mode: {
              status: "idle",
              active: false,
              gpu_owner: "hermes",
              model: "gpt-oss:20b",
              model_loaded: true,
              local_inference_ready: true,
              inference_path: "local",
            },
          },
          wolf: {
            api_enabled: true,
            clients: [{ name: "Desktop" }],
            pending_pair_requests: [{ name: "Laptop", pair_secret: "secret-1" }],
            sessions: [],
            apps: [{ name: "Steam" }],
          },
          firewall: { active_unlocks: [{ ip: "181.1.87.186" }] },
        });
      }
      if (url.includes("/api/account/overview")) return jsonResponse(overview());
      if (url.includes("/api/admin/access/requests")) return jsonResponse({ requests: [] });
      if (url.includes("/api/admin/access/flags")) return jsonResponse({ flags: [] });
      return jsonResponse({});
    });
    const { dashboard, wrapper } = mountDashboard();
    await flushPromises();

    expect(dashboard.wolf.status).toBe("ready");
    expect(dashboard.wolf.canControlGpu).toBe(true);
    expect(dashboard.wolf.gpuOwner).toBe("hermes");
    expect(dashboard.wolf.localModel).toBe("gpt-oss:20b");
    expect(dashboard.wolf.localModelLoaded).toBe(true);
    expect(dashboard.wolf.localInferenceReady).toBe(true);
    expect(dashboard.wolf.inferencePath).toBe("local");
    expect(dashboard.wolf.clients[0].name).toBe("Desktop");
    expect(dashboard.wolf.pendingPairRequests[0].pair_secret).toBe("secret-1");

    await dashboard.unlockWolf();
    dashboard.wolf.pinInputs["secret-1"] = "1234";
    await dashboard.pairWolf("secret-1");
    dashboard.wolf.selectedGame = "desktop";
    dashboard.wolf.note = "now";
    await dashboard.setWolfGameMode("start");
    await dashboard.setWolfGameMode("stop");
    dashboard.wolf.manualIp = "5.6.7.8";
    dashboard.wolf.manualUser = "olya";
    await dashboard.adminUnlockWolfIp();

    expect(seen.some((item) => item.url.includes("/api/account/wolf?source_ip=181.1.87.186"))).toBe(true);
    expect(JSON.parse(seen.find((item) => item.url.includes("/firewall/unlock")).body)).toEqual({
      ip: "181.1.87.186",
    });
    expect(JSON.parse(seen.find((item) => item.url.includes("/pairing/submit-pin")).body)).toEqual({
      pair_secret: "secret-1",
      pin: "1234",
      source_ip: "181.1.87.186",
    });
    expect(JSON.parse(seen.find((item) => item.url.includes("/game-mode/start")).body)).toEqual({
      game: "desktop",
      note: "now",
    });
    expect(JSON.parse(seen.find((item) => item.url.includes("/admin/firewall/unlock")).body)).toEqual({
      ip: "5.6.7.8",
      target_user: "olya",
    });
    wrapper.unmount();
  });

  it("handles Wolf login and action failures", async () => {
    installFetch((url) => {
      if (url.includes("api.ipify.org")) return jsonResponse({ ip: "181.1.87.186" });
      if (url.includes("/api/account/wolf/firewall/unlock")) return jsonResponse({ error: "unlock failed" }, 500);
      if (url.includes("/api/account/wolf/pairing/submit-pin")) return jsonResponse({ error: "pair failed" }, 500);
      if (url.includes("/api/account/wolf/game-mode/start")) return jsonResponse({ error: "mode failed" }, 500);
      if (url.includes("/api/account/wolf/admin/firewall/unlock")) return jsonResponse({ error: "manual failed" }, 500);
      if (url.includes("/api/account/wolf")) return jsonResponse({ error: "status 502" }, 502);
      if (url.includes("/api/account/overview")) return jsonResponse(overview());
      if (url.includes("/api/admin/access/requests")) return jsonResponse({ requests: [] });
      if (url.includes("/api/admin/access/flags")) return jsonResponse({ flags: [] });
      return jsonResponse({});
    });
    const { dashboard, wrapper } = mountDashboard();
    await flushPromises();

    expect(dashboard.wolf.error).toBe("Ariadne is busy. Please try again in a moment.");

    await dashboard.unlockWolf();
    expect(dashboard.wolf.error).toBe("unlock failed");

    dashboard.wolf.pinInputs.secret = "1234";
    await dashboard.pairWolf("secret");
    expect(dashboard.wolf.error).toBe("pair failed");

    await dashboard.setWolfGameMode("start");
    expect(dashboard.wolf.error).toBe("mode failed");

    dashboard.wolf.manualIp = "5.6.7.8";
    await dashboard.adminUnlockWolfIp();
    expect(dashboard.wolf.error).toBe("manual failed");

    auth.authenticated = false;
    await dashboard.refreshWolf();
    expect(dashboard.wolf.status).toBe("login required");
    wrapper.unmount();
  });

  it("handles service action warnings and failures", async () => {
    const rotateResponses = [
      { password: "mail-a", sync_enabled: false },
      { password: "mail-b", sync_enabled: true, sync_ok: false, sync_error: "sync slow" },
      { error: "ariadne unavailable" },
    ];
    installFetch((url) => {
      if (url.includes("/mailu/rotate")) {
        const body = rotateResponses.shift();
        return body.error ? jsonResponse(body, 503, "Service Unavailable") : jsonResponse(body);
      }
      if (url.includes("/wger/reset")) return jsonResponse({ error: "status 502" }, 502, "Bad Gateway");
      if (url.includes("/firefly/reset")) return jsonResponse({}, 500, "Server Error");
      if (url.includes("/nextcloud/mail/sync")) return jsonResponse({ error: "status 503" }, 503, "Service Unavailable");
      if (url.includes("/api/account/overview")) return jsonResponse(overview());
      if (url.includes("/api/admin/access/requests")) return jsonResponse({ requests: [] });
      if (url.includes("/api/admin/access/flags")) return jsonResponse({ flags: [] });
      return jsonResponse({});
    });
    const { dashboard, wrapper } = mountDashboard();
    await flushPromises();

    await dashboard.rotateMailu();
    expect(dashboard.mailu.error).toContain("Mail sync is not configured");
    await dashboard.rotateMailu();
    expect(dashboard.mailu.error).toBe("sync slow");
    await dashboard.rotateMailu();
    expect(dashboard.mailu.error).toBe("Ariadne is busy. Please try again in a moment.");

    await dashboard.resetWger();
    expect(dashboard.wger.error).toBe("Ariadne is busy. Please try again in a moment.");

    await dashboard.resetFirefly();
    expect(dashboard.firefly.error).toBe("status 500");

    await dashboard.syncNextcloudMail();
    expect(dashboard.nextcloudMail.error).toContain("Refresh in a moment");

    installFetch((url) => {
      if (url.includes("/nextcloud/mail/sync")) return jsonResponse({ error: "plain sync failed" }, 500, "Server Error");
      if (url.includes("/api/account/overview")) return jsonResponse(overview());
      if (url.includes("/api/admin/access/requests")) return jsonResponse({ requests: [] });
      if (url.includes("/api/admin/access/flags")) return jsonResponse({ flags: [] });
      return jsonResponse({});
    });
    await dashboard.syncNextcloudMail();
    expect(dashboard.nextcloudMail.error).toBe("plain sync failed");
    wrapper.unmount();
  });

  it("approves, denies, and copies admin/account values", async () => {
    const seen = [];
    installFetch((url, options) => {
      seen.push({ url, body: options.body });
      if (url.includes("/approve") || url.includes("/deny")) return jsonResponse({ ok: true });
      if (url.includes("/api/admin/access/requests")) return jsonResponse({ requests: [] });
      if (url.includes("/api/admin/access/flags")) return jsonResponse({ flags: ["media"] });
      if (url.includes("/api/account/overview")) return jsonResponse(overview());
      return jsonResponse({});
    });
    const { dashboard, wrapper } = mountDashboard();
    await flushPromises();

    dashboard.admin.selectedFlags.ada = ["media"];
    dashboard.admin.notes.ada = " approve me ";
    await dashboard.approve("ada");
    expect(JSON.parse(seen.find((item) => item.url.includes("/approve")).body)).toEqual({ flags: ["media"], note: "approve me" });

    dashboard.admin.notes.ada = " no ";
    await dashboard.deny("ada");
    expect(JSON.parse(seen.find((item) => item.url.includes("/deny")).body)).toEqual({ note: "no" });

    await dashboard.copy("mail", "secret");
    expect(navigator.clipboard.writeText).toHaveBeenCalledWith("secret");
    expect(dashboard.copied.mail).toBe(true);
    jest.advanceTimersByTime(1500);
    expect(dashboard.copied.mail).toBe(false);

    Object.defineProperty(navigator, "clipboard", {
      configurable: true,
      value: { writeText: jest.fn(async () => { throw new Error("blocked"); }) },
    });
    Object.defineProperty(document, "execCommand", { configurable: true, value: jest.fn(() => true) });
    await dashboard.copy("fallback", "secret2");
    expect(document.execCommand).toHaveBeenCalledWith("copy");
    expect(dashboard.copied.fallback).toBe(true);
    jest.advanceTimersByTime(1500);
    expect(dashboard.copied.fallback).toBe(false);

    Object.defineProperty(navigator, "clipboard", { configurable: true, value: undefined });
    await dashboard.copy("direct-fallback", "secret3");
    expect(dashboard.copied["direct-fallback"]).toBe(true);

    Object.defineProperty(document, "execCommand", { configurable: true, value: jest.fn(() => { throw new Error("no copy"); }) });
    await dashboard.copy("ignored", "secret4");
    expect(dashboard.copied.ignored).toBeUndefined();

    await dashboard.copy("empty", "");
    expect(dashboard.copied.empty).toBeUndefined();
    wrapper.unmount();
  });

  it("surfaces overview and admin loading failures", async () => {
    installFetch((url) => {
      if (url.includes("/api/account/overview")) return jsonResponse({ error: "overview down" }, 500, "Server Error");
      if (url.includes("/api/admin/access/requests")) return jsonResponse({}, 403, "Forbidden");
      if (url.includes("/api/admin/access/flags")) return jsonResponse({}, 500, "Server Error");
      return jsonResponse({});
    });
    const { dashboard, wrapper } = mountDashboard();
    await flushPromises();

    expect(dashboard.mailu.status).toBe("unavailable");
    expect(dashboard.mailu.error).toContain("overview down");
    expect(dashboard.admin.enabled).toBe(false);
    expect(dashboard.admin.flags).toEqual([]);
    expect(dashboard.admin.error).toBe("status 500");
    wrapper.unmount();
  });

  it("handles admin request errors and forbidden flag lists", async () => {
    installFetch((url) => {
      if (url.includes("/api/account/overview")) return jsonResponse(overview());
      if (url.includes("/api/admin/access/requests")) return jsonResponse({}, 500, "Server Error");
      if (url.includes("/api/admin/access/flags")) return jsonResponse({}, 403, "Forbidden");
      return jsonResponse({});
    });
    const { dashboard, wrapper } = mountDashboard();
    await flushPromises();

    expect(dashboard.admin.enabled).toBe(false);
    expect(dashboard.admin.requests).toEqual([]);
    expect(dashboard.admin.flags).toEqual([]);
    expect(dashboard.admin.error).toBe("status 500");
    wrapper.unmount();
  });

  it("reports admin action failures without leaving acting locks stuck", async () => {
    installFetch((url) => {
      if (url.includes("/approve")) return jsonResponse({ error: "approve failed" }, 500, "Server Error");
      if (url.includes("/deny")) return jsonResponse({}, 500, "Server Error");
      if (url.includes("/api/account/overview")) return jsonResponse(overview());
      if (url.includes("/api/admin/access/requests")) return jsonResponse({ requests: [] });
      if (url.includes("/api/admin/access/flags")) return jsonResponse({ flags: [] });
      return jsonResponse({});
    });
    const { dashboard, wrapper } = mountDashboard();
    await flushPromises();

    await dashboard.approve("ada");
    expect(dashboard.admin.error).toBe("approve failed");
    expect(dashboard.admin.acting.ada).toBe(false);

    await dashboard.deny("ada");
    expect(dashboard.admin.error).toBe("status 500");
    expect(dashboard.admin.acting.ada).toBe(false);
    wrapper.unmount();
  });
});
