import type { Community } from "./types";
import { homeDir } from "@tauri-apps/api/path";
import { setLocalStorageItemWithRecovery } from "@/shared/lib/localStorageQuota";
import { getStorageItem, removeStorageItem } from "@/shared/lib/safeStorage";

const COMMUNITIES_KEY = "buzz-communities";
const ACTIVE_COMMUNITY_KEY = "buzz-active-community-id";
const LEGACY_WORKSPACES_KEY = "buzz-workspaces";
const LEGACY_ACTIVE_WORKSPACE_KEY = "buzz-active-workspace-id";
const COMMUNITY_DISCOVERY_AFTER_LEAVE_KEY =
  "buzz-community-discovery-after-leave";

/**
 * Expand a leading `~` to the user's home directory. The backend rejects
 * `~`-prefixed paths (`std::fs` does not expand the shell tilde), so the UI
 * resolves it before save. Returns non-`~` input unchanged. Empty/whitespace
 * input returns `undefined` so callers can clear the override.
 */
export async function expandTilde(input: string): Promise<string | undefined> {
  const trimmed = input.trim();
  if (!trimmed) {
    return undefined;
  }
  if (trimmed === "~") {
    return homeDir();
  }
  if (trimmed.startsWith("~/")) {
    const home = await homeDir();
    const base = home.endsWith("/") ? home.slice(0, -1) : home;
    return `${base}/${trimmed.slice(2)}`;
  }
  return trimmed;
}

export function migrateLegacyCommunityStorage(
  storage: Storage = localStorage,
): void {
  try {
    if (storage.getItem(COMMUNITIES_KEY) === null) {
      const legacyCommunities = storage.getItem(LEGACY_WORKSPACES_KEY);
      if (legacyCommunities !== null) {
        storage.setItem(COMMUNITIES_KEY, legacyCommunities);
      }
    }
    if (storage.getItem(ACTIVE_COMMUNITY_KEY) === null) {
      const legacyActiveCommunity = storage.getItem(
        LEGACY_ACTIVE_WORKSPACE_KEY,
      );
      if (legacyActiveCommunity !== null) {
        storage.setItem(ACTIVE_COMMUNITY_KEY, legacyActiveCommunity);
      }
    }
  } catch (error) {
    // WebKit throws SecurityError from getItem when storage access is denied
    // for the origin (block/buzz#5078). Fencing here so the app can still
    // boot with an empty/default community list instead of a blank window.
    console.warn(
      "[communityStorage] migrateLegacyCommunityStorage failed (storage denied?):",
      error,
    );
  }
}

export function loadCommunities(): Community[] {
  try {
    migrateLegacyCommunityStorage();
    const raw = getStorageItem(COMMUNITIES_KEY);
    if (!raw) {
      return [];
    }
    const parsed: unknown = JSON.parse(raw);
    if (!Array.isArray(parsed)) {
      return [];
    }
    if (parsed.length > 0) {
      removeStorageItem(COMMUNITY_DISCOVERY_AFTER_LEAVE_KEY);
    }
    // Migration: older builds stored the user's `nsec` in localStorage and
    // re-applied it to the backend on every reload, which silently overwrote
    // any `import_identity` result with the original generated key. The
    // on-disk `identity.key` file is the only source of truth now. Strip
    // any lingering `nsec` from existing entries on read and persist the
    // cleaned list back so it cannot leak into future sessions.
    let didStrip = false;
    const cleaned = (parsed as Array<Record<string, unknown>>).map((entry) => {
      if (entry && typeof entry === "object" && "nsec" in entry) {
        const { nsec: _nsec, ...rest } = entry;
        didStrip = true;
        return rest;
      }
      return entry;
    }) as Community[];
    if (didStrip) {
      setLocalStorageItemWithRecovery(COMMUNITIES_KEY, JSON.stringify(cleaned));
    }
    return cleaned;
  } catch {
    return [];
  }
}

export function saveCommunities(communities: Community[]): boolean {
  const didSave = setLocalStorageItemWithRecovery(
    COMMUNITIES_KEY,
    JSON.stringify(communities),
  );
  if (didSave && communities.length > 0) {
    localStorage.removeItem(COMMUNITY_DISCOVERY_AFTER_LEAVE_KEY);
  }
  return didSave;
}

export function loadCommunityDiscoveryAfterLeave(
  storage: Storage = localStorage,
): boolean {
  try {
    return storage.getItem(COMMUNITY_DISCOVERY_AFTER_LEAVE_KEY) === "1";
  } catch (error) {
    // block/buzz#5078 — storage access can be denied for the origin; degrade
    // to the default ("didn't just leave") instead of crashing the boot path.
    console.warn(
      "[communityStorage] loadCommunityDiscoveryAfterLeave failed:",
      error,
    );
    return false;
  }
}

export function markCommunityDiscoveryAfterLeave(
  storage: Storage = localStorage,
): boolean {
  if (typeof window !== "undefined" && storage === window.localStorage) {
    return setLocalStorageItemWithRecovery(
      COMMUNITY_DISCOVERY_AFTER_LEAVE_KEY,
      "1",
    );
  }
  try {
    storage.setItem(COMMUNITY_DISCOVERY_AFTER_LEAVE_KEY, "1");
    return true;
  } catch {
    return false;
  }
}

export function clearCommunityStorage(storage: Storage = localStorage): void {
  storage.removeItem(COMMUNITIES_KEY);
  storage.removeItem(ACTIVE_COMMUNITY_KEY);
  storage.removeItem(LEGACY_WORKSPACES_KEY);
  storage.removeItem(LEGACY_ACTIVE_WORKSPACE_KEY);
}

export function loadActiveCommunityId(): string | null {
  migrateLegacyCommunityStorage();
  // block/buzz#5078 — WebKit can throw SecurityError from a denied-storage
  // getItem. Fail closed so the boot path renders the default community UI
  // instead of unmounting the root.
  return getStorageItem(ACTIVE_COMMUNITY_KEY);
}

export function saveActiveCommunityId(id: string): boolean {
  return setLocalStorageItemWithRecovery(ACTIVE_COMMUNITY_KEY, id);
}

export function normalizeRelayUrl(url: string): string {
  if (!url.startsWith("ws://") && !url.startsWith("wss://")) {
    return `wss://${url}`;
  }
  return url;
}

function isLocalRelayHost(hostname: string): boolean {
  return ["localhost", "127.0.0.1", "[::1]", "0.0.0.0"].includes(hostname);
}

export function shouldAutoConnectDefaultRelay(relayUrl: string): boolean {
  try {
    const parsed = new URL(relayUrl);
    return (
      (parsed.protocol === "ws:" || parsed.protocol === "wss:") &&
      !isLocalRelayHost(parsed.hostname)
    );
  } catch {
    return false;
  }
}

export function deriveCommunityName(relayUrl: string): string {
  try {
    const url = new URL(
      relayUrl.replace("ws://", "http://").replace("wss://", "https://"),
    );
    const host = url.hostname;
    if (isLocalRelayHost(host)) {
      return "Local Dev";
    }
    const parts = host.split(".");
    // Detect staging environments (e.g. buzz-oss.stage.blox.sqprod.co)
    if (parts.some((p) => p === "stage" || p === "staging")) {
      return "Buzz (staging)";
    }
    // Use the first subdomain segment or the domain itself
    if (parts.length >= 2) {
      return parts[0] === "relay" ? parts[1] : parts[0];
    }
    return host;
  } catch {
    return "Community";
  }
}

export function initFirstCommunity(
  relayUrl: string,
  pubkey: string,
  name?: string,
): Community | null {
  const normalizedUrl = normalizeRelayUrl(relayUrl);
  const trimmedName = name?.trim();
  const community: Community = {
    id: crypto.randomUUID(),
    name: trimmedName || deriveCommunityName(normalizedUrl),
    relayUrl: normalizedUrl,
    // Compiled default relays must admit the first token-less connection; there
    // is no invite-token prompt on this auto-connect path.
    pubkey,
    addedAt: new Date().toISOString(),
  };
  // block/buzz#5078 — read the prior active id through the throw-safe helper;
  // a denied-storage origin would otherwise kill onboarding before a single
  // write is attempted.
  const previousActiveCommunityId = getStorageItem(ACTIVE_COMMUNITY_KEY);
  const didSaveActiveCommunity = saveActiveCommunityId(community.id);
  if (!didSaveActiveCommunity) {
    return null;
  }

  if (!saveCommunities([community])) {
    // A failed setItem leaves the existing communities value untouched. Roll
    // back only the active-ID write so inconsistent pre-existing data is never
    // destroyed while recovering from a quota failure.
    try {
      if (previousActiveCommunityId === null) {
        localStorage.removeItem(ACTIVE_COMMUNITY_KEY);
      } else {
        localStorage.setItem(ACTIVE_COMMUNITY_KEY, previousActiveCommunityId);
      }
    } catch {
      // Best effort: persistence is already unavailable, and callers will stay
      // on setup instead of reloading.
    }
    return null;
  }

  return community;
}
