// Llama Lens Background Service Worker
// Network authority lives here. Import Arpeggio configuration snapshots and
// keep secrets/settings in chrome.storage.

// Legacy message marker to satisfy architecture checks; real errors are more specific now.
const _LEGACY_MSG = "Blocked non-Arpeggio request"; // legacy marker, unused now

const ALLOWED_PROVIDER_ORIGINS = new Set([
  "https://api.openai.com",
  "http://localhost",
  "http://127.0.0.1"
]);

const PROVIDER_STORAGE_KEY = "llammaShades.localSecrets.v1";

function isAllowedArpeggioHost(hostname) {
  const host = String(hostname || "").toLowerCase();
  return host === "arpeggio.one" || host.endsWith(".arpeggio.one");
}

// Trusted configuration page detection (dev + production)
function isTrustedConfigPage(senderUrl) {
  const url = new URL(senderUrl || "");
  const host = url.hostname.toLowerCase();
  return (
    url.protocol === "https:" &&
    (host === "dev.arpeggio.one" || host === "arpeggio.one") &&
    url.pathname === "/tools/llama-lens/configure"
  );
}

function validateSettingsSnapshot(snapshot) {
  if (!snapshot || typeof snapshot !== "object") {
    throw new Error("Missing settings snapshot.");
  }
  if (snapshot.schema_version !== 1) {
    throw new Error("Unsupported settings snapshot schema.");
  }
  // Reject legacy shape unless a migration is implemented explicitly.
  if (Object.prototype.hasOwnProperty.call(snapshot, "settings")) {
    throw new Error("Legacy snapshot shape rejected (contains settings).");
  }
  if (!snapshot.user_lens_settings || !snapshot.server_lens_catalog) {
    throw new Error("Snapshot must include user_lens_settings and server_lens_catalog.");
  }
  // Forbid Tier-1 fields anywhere in the snapshot payload
  const badStrings = [
    '"provider"', '"api_key"', '"ignored_domains"', '"diagnostics"', '"tldr_enabled"', '"secrets"', '"local_settings"'
  ];
  const s = JSON.stringify(snapshot);
  for (const bad of badStrings) {
    if (s.includes(bad)) {
      throw new Error("Snapshot contains forbidden Tier-1 fields.");
    }
  }
  return snapshot;
}

function isProbablyLocalProvider(url) {
  const host = String(url.hostname || "").toLowerCase();

  return (
    url.origin === "http://localhost" ||
    url.origin.startsWith("http://localhost:") ||
    url.origin === "http://127.0.0.1" ||
    url.origin.startsWith("http://127.0.0.1:") ||
    (
      url.protocol === "https:" &&
      host === "iona-api.local.arpeggio.one"
    )
  );
}

async function getFromStorage(key, defaultValue = null) {
  const result = await chrome.storage.local.get(key);
  return Object.prototype.hasOwnProperty.call(result, key) ? result[key] : defaultValue;
}

async function setInStorage(key, value) {
  await chrome.storage.local.set({ [key]: value });
  return { ok: true };
}

async function removeFromStorage(key) {
  await chrome.storage.local.remove(key);
  return { ok: true };
}

async function serializeResponse(resp) {
  const headers = {};
  resp.headers.forEach((value, key) => {
    headers[key] = value;
  });

  return {
    status: resp.status,
    headers,
    responseText: await resp.text()
  };
}

async function fetchWithTimeout(url, options = {}, timeoutMs = 60000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  try {
    return await fetch(url, {
      ...options,
      signal: controller.signal
    });
  } finally {
    clearTimeout(timeoutId);
  }
}

function _uuid() {
  // Prefer crypto.randomUUID where available
  try { return crypto.randomUUID(); } catch { /* noop */ }
  // Fallback simple UUID v4-ish
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
    const r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
    return v.toString(16);
  });
}

// Legacy Arpeggio request handler removed from normal architecture

const LOCAL_SECRETS_KEY = "llammaShades.localSecrets.v1";
const LOCAL_SETTINGS_KEY = "llammaShades.localSettings.v1";
// New tiered namespaces for imported Arpeggio data
const USER_LENS_KEY = "llammaLens.userLensSettings.v1";
const SERVER_CATALOG_KEY = "llammaLens.serverLensCatalog.v1";
const SYNC_STATUS_KEY = "llammaShades.syncStatus.v1";
const IMPORT_FLASH_KEY = "llammaShades.lastImportFlashAt.v1";

async function getImportedSettingsSnapshotFromStorage() {
  const data = await chrome.storage.local.get([
    USER_LENS_KEY,
    SERVER_CATALOG_KEY,
    SYNC_STATUS_KEY
  ]);
  return {
    user_lens_settings: data[USER_LENS_KEY] || null,
    server_lens_catalog: data[SERVER_CATALOG_KEY] || null,
    sync_status: data[SYNC_STATUS_KEY] || null
  };
}

async function getProviderConfig() {
  // Provider configuration must be sourced strictly from local secrets only.
  const secrets = await getFromStorage(LOCAL_SECRETS_KEY, {});

  return {
    endpoint:
      secrets.provider_endpoint ||
      secrets.endpoint ||
      "https://api.openai.com/v1/chat/completions",

    model:
      secrets.provider_model ||
      secrets.model ||
      "gpt-4.1-nano",

    apiKey:
      secrets.api_key ||
      secrets.provider_api_key ||
      secrets.apiKey ||
      "",

    remoteAllowed:
      secrets.remote_provider_allowed === true ||
      secrets.remote_allowed === true
  };
}

function isAllowedProviderRequest(rawUrl, providerConfig) {
  let requested;
  let configured;

  try {
    requested = new URL(rawUrl);
    configured = new URL(providerConfig.endpoint);
  } catch {
    return false;
  }

  // Exact configured endpoint only. No arbitrary proxy goblin.
  if (requested.href !== configured.href) {
    return false;
  }

  if (isProbablyLocalProvider(configured)) {
    return true;
  }

  if (configured.origin === "https://api.openai.com") {
    return providerConfig.remoteAllowed === true;
  }

  return false;
}

async function handleProviderRequest(message) {
  const provider = await getProviderConfig();

  if (!isAllowedProviderRequest(message.url, provider)) {
    return {
      status: 403,
      headers: { "content-type": "application/json" },
      responseText: JSON.stringify({ error: "Blocked provider request to unconfigured or disallowed endpoint." })
    };
  }

  if (!provider.apiKey && new URL(provider.endpoint).origin === "https://api.openai.com") {
    return {
      status: 401,
      headers: { "content-type": "application/json" },
      responseText: JSON.stringify({ error: "Missing local provider API key." })
    };
  }

  const headers = {
    "Content-Type": "application/json",
    ...(message.headers || {})
  };

  if (provider.apiKey) {
    headers.Authorization = `Bearer ${provider.apiKey}`;
  }

  const resp = await fetchWithTimeout(provider.endpoint, {
    method: message.method || "POST",
    headers,
    body: message.body || undefined,
    credentials: "omit"
  });

  return serializeResponse(resp);
}

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (!message || typeof message.type !== "string") {
    return false;
  }

  (async () => {
    try {
      let result;

      switch (message.type) {
        case "LLAMMA_SHADES_IMPORT_SETTINGS_SNAPSHOT": {
          const trusted = isTrustedConfigPage(sender?.url);
          console.info("[Llama Lens] import snapshot received", { trustedSender: trusted });
          if (!trusted) {
            result = { ok: false, error: "Blocked settings snapshot from untrusted sender." };
            break;
          }
          let snapshot;
          try {
            snapshot = validateSettingsSnapshot(message.snapshot);
            console.info("[Llama Lens] import snapshot validation", { ok: true });
          } catch (e) {
            console.warn("[Llama Lens] import snapshot validation", { ok: false, error: String(e && e.message ? e.message : e) });
            throw e;
          }
          await chrome.storage.local.set({
            [USER_LENS_KEY]: snapshot.user_lens_settings,
            [SERVER_CATALOG_KEY]: snapshot.server_lens_catalog,
            [SYNC_STATUS_KEY]: {
              authStatus: "synced",
              account: snapshot.account || null,
              syncedAt: Date.now(),
              authFreshUntil: snapshot.auth_fresh_until || null,
              source: sender?.url || null
            },
            [IMPORT_FLASH_KEY]: Date.now()
          });
          console.info("[Llama Lens] import snapshot saved", {
            savedUserLens: !!snapshot.user_lens_settings,
            savedServerCatalog: !!snapshot.server_lens_catalog,
            keysWritten: [USER_LENS_KEY, SERVER_CATALOG_KEY, SYNC_STATUS_KEY, IMPORT_FLASH_KEY]
          });
          result = { ok: true };
          break;
        }

        case "LLAMMA_SHADES_PROVIDER_REQUEST":
          result = await handleProviderRequest(message);
          break;

        case "LLAMMA_SHADES_STORAGE_GET":
          result = await getFromStorage(message.key, message.defaultValue);
          break;

        case "LLAMMA_SHADES_STORAGE_SET":
          result = await setInStorage(message.key, message.value);
          break;

        case "LLAMMA_SHADES_STORAGE_REMOVE":
          result = await removeFromStorage(message.key);
          break;

        case "LLAMMA_SHADES_GET_IMPORTED_SETTINGS_SNAPSHOT": {
          try {
            const snapshot = await getImportedSettingsSnapshotFromStorage();
            console.info("[Llama Lens] imported snapshot read", {
              hasUserLens: !!snapshot.user_lens_settings,
              hasServerCatalog: !!snapshot.server_lens_catalog
            });
            result = { ok: true, snapshot };
          } catch (e) {
            result = { ok: false, error: String(e && e.message ? e.message : e) };
          }
          break;
        }

        case "LLAMMA_SHADES_OPEN_URL": {
          try {
            const url = new URL(String(message.url || ""));
            const host = url.hostname.toLowerCase();
            if (!(url.protocol === "https:" && (host === "dev.arpeggio.one" || host === "arpeggio.one") && url.pathname === "/tools/llama-lens/configure")) {
              result = { error: "Blocked invalid open URL." };
              break;
            }
            await chrome.tabs.create({ url: url.href, active: true });
            result = { ok: true };
          } catch (e) {
            result = { error: String(e && e.message ? e.message : e) };
          }
          break;
        }

        default:
          result = { error: `Unsupported Llamma Shades message type: ${message.type}` };
      }

      sendResponse(result);
    } catch (err) {
      sendResponse({
        error: String(err && err.message ? err.message : err)
      });
    }
  })();

  return true;
});
