/// Content-script-callable message handlers.
///
/// Origin is always derived from sender.tab.url — never trust fields on msg.
/// Router has already verified sender.frameId === 0 (top-frame only) and
/// sender.tab !== undefined.

import type { ContentMessage, Response } from '../../shared/messages';
import type { Item, Manifest } from '../../shared/types';
import type { GitHost } from '../git-host';
import * as vault from '../vault';
import { itemToManifestEntry } from '../vault';
import * as session from '../session';
import { loadDeviceSettings, loadBlacklist, saveBlacklist } from '../storage';

export interface ContentState {
  manifest: Manifest | null;
  gitHost: GitHost | null;
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  wasm: any;
}

export async function handle(
  msg: ContentMessage,
  state: ContentState,
  sender: chrome.runtime.MessageSender,
): Promise<Response> {
  const senderHost = safeHostname(sender.tab?.url ?? '');
  if (!senderHost) return { ok: false, error: 'invalid_sender_url' };

  switch (msg.type) {
    case 'get_autofill_candidates': {
      if (!state.manifest) return { ok: false, error: 'vault_locked' };
      return {
        ok: true,
        data: { candidates: vault.findByHostname(state.manifest, senderHost) },
      };
    }

    case 'get_credentials': {
      const handle = session.getCurrent();
      if (!handle || !state.gitHost || !state.manifest) return { ok: false, error: 'vault_locked' };

      const item = await vault.fetchAndDecryptItem(state.gitHost, handle, msg.id);
      if (item.core.type !== 'login') return { ok: false, error: 'not_a_login' };
      const itemHost = safeHostname(item.core.url ?? '');
      if (!itemHost || itemHost !== senderHost) return { ok: false, error: 'origin_mismatch' };

      // TOFU origin-ack check (VaultSettings.autofill_origin_acks):
      const settings = await vault.fetchAndDecryptSettings(state.gitHost, handle);
      const acks = settings.autofill_origin_acks ?? {};
      if (!(senderHost in acks)) {
        return { ok: true, data: { requires_ack: true, hostname: senderHost } };
      }

      return {
        ok: true,
        data: {
          username: item.core.username ?? '',
          password: item.core.password ?? '',
        },
      };
    }

    case 'check_credential': {
      const handle = session.getCurrent();
      if (!handle || !state.gitHost || !state.manifest) {
        return { ok: true, data: { action: 'skip' } };
      }

      // Settings-gating: capture off or site blacklisted → skip.
      const captureSettings = await loadDeviceSettings();
      if (!captureSettings.captureEnabled) return { ok: true, data: { action: 'skip' } };

      const blacklist = await loadBlacklist();
      if (blacklist.includes(senderHost)) return { ok: true, data: { action: 'skip' } };

      const candidates = vault.findByHostname(state.manifest, senderHost);
      if (candidates.length === 0) return { ok: true, data: { action: 'save' } };

      for (const [itemId, entry] of candidates) {
        if (entry.type !== 'login') continue;
        const full = await vault.fetchAndDecryptItem(state.gitHost, handle, itemId);
        if (full.core.type !== 'login') continue;
        if (full.core.username === msg.username) {
          if (full.core.password === msg.password) return { ok: true, data: { action: 'skip' } };
          return { ok: true, data: { action: 'update', entryId: itemId, entryName: entry.title } };
        }
      }
      return { ok: true, data: { action: 'save' } };
    }

    case 'blacklist_site': {
      const bl = await loadBlacklist();
      if (!bl.includes(senderHost)) {
        bl.push(senderHost);
        await saveBlacklist(bl);
      }
      return { ok: true };
    }

    case 'capture_save_login': {
      const handle = session.getCurrent();
      if (!handle || !state.gitHost || !state.manifest) return { ok: false, error: 'vault_locked' };

      // Look for an existing login for this origin + username. Origin is
      // always senderHost (derived from sender.tab.url by the router) — the
      // content script cannot influence which host we bind to.
      const candidates = vault.findByHostname(state.manifest, senderHost);
      for (const [id, entry] of candidates) {
        if (entry.type !== 'login') continue;
        const full = await vault.fetchAndDecryptItem(state.gitHost, handle, id);
        if (full.core.type !== 'login') continue;
        if (full.core.username === msg.username) {
          // Defense in depth: verify the existing item's own URL hostname
          // matches senderHost. If it doesn't (e.g. manifest icon_hint
          // drifted from core.url), refuse to mutate — updating here would
          // silently bind a password to the wrong origin.
          const existingHost = safeHostname(full.core.url ?? '');
          if (existingHost !== senderHost) return { ok: false, error: 'origin_mismatch' };

          // Update only the password field + modified timestamp.
          const updated: Item = {
            ...full,
            modified: Math.floor(Date.now() / 1000),
            core: { ...full.core, password: msg.password },
          };
          await vault.encryptAndWriteItem(state.gitHost, handle, id, updated, `capture: update ${existingHost}`);
          state.manifest.items[id] = itemToManifestEntry(updated);
          await vault.encryptAndWriteManifest(state.gitHost, handle, state.manifest, `manifest: update ${existingHost}`);
          return { ok: true, data: { action: 'updated', id } };
        }
      }

      // No match → create a new Login item bound to senderHost. Title
      // defaults to the hostname; url is the sender's full origin when we
      // have it, otherwise derived from senderHost.
      const now = Math.floor(Date.now() / 1000);
      const newId = state.wasm.new_item_id();
      const senderOrigin = (() => {
        try { return sender.tab?.url ? new URL(sender.tab.url).origin : `https://${senderHost}`; }
        catch { return `https://${senderHost}`; }
      })();
      const item: Item = {
        id: newId,
        title: senderHost,
        type: 'login',
        tags: [],
        favorite: false,
        created: now,
        modified: now,
        core: {
          type: 'login',
          username: msg.username,
          password: msg.password,
          url: senderOrigin,
        },
        sections: [],
        attachments: [],
        field_history: {},
      };
      await vault.encryptAndWriteItem(state.gitHost, handle, newId, item, `capture: add ${senderHost}`);
      state.manifest.items[newId] = itemToManifestEntry(item);
      await vault.encryptAndWriteManifest(state.gitHost, handle, state.manifest, `manifest: add ${senderHost}`);
      return { ok: true, data: { action: 'added', id: newId } };
    }
  }
}

function safeHostname(url: string): string | undefined {
  try { return new URL(url).hostname; } catch { return undefined; }
}
