import type { ChatMessage } from '@genesislcap/foundation-ai';
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import { aiAssistantSlice, createDefaultSessionState } from '../ai-assistant-slice';
import { WebStorageSessionProvider } from './session-persistence-provider';
import {
  createPersistedSession,
  isRestorableSession,
  toLoadSessionPayload,
  trimToRestorableBoundary,
} from './session-snapshot';

// GENC-1351 Phase 1 integration: the page-reload cycle the element wires —
// save on idle → reload drops the in-memory store + provider → restore hydrates
// a fresh store. Exercises the REAL provider + schema + reducer composed; the
// custom-element glue (connectedCallback → maybeRestoreSession) is covered by a
// manual app test.

/** In-memory Storage that survives the simulated reload (like sessionStorage does). */
function fakeStorage(): Storage {
  const m = new Map<string, string>();
  return {
    get length() {
      return m.size;
    },
    clear: () => m.clear(),
    getItem: (k: string) => (m.has(k) ? m.get(k)! : null),
    key: (i: number) => [...m.keys()][i] ?? null,
    removeItem: (k: string) => {
      m.delete(k);
    },
    setItem: (k: string, v: string) => {
      m.set(k, v);
    },
  } as Storage;
}

const asMsg = (o: Record<string, unknown>): ChatMessage => o as unknown as ChatMessage;
const reduce = aiAssistantSlice.reducer;
const { loadSession } = aiAssistantSlice.actions;

const Suite = createLogicSuite('session-persistence-integration');

Suite('save → reload → restore brings back messages, selection, counters', async () => {
  const storage = fakeStorage(); // persists across the simulated reload
  const host = 'app.example';
  const key = 'assistant::Title';

  // 1. A live session reaches idle with history + a pinned agent.
  const messages = [
    asMsg({ role: 'user', content: 'book a trade' }),
    asMsg({ role: 'assistant', content: 'sure' }),
  ];
  await new WebStorageSessionProvider(storage).save(
    key,
    createPersistedSession({
      sessionKey: key,
      host,
      messages,
      activeAgentName: 'Guided Booking',
      pinnedAgentName: 'Guided Booking',
      flowOwnerAgentName: null,
      sessionCostUsd: 0.5,
      contextTokens: 800,
      contextLimit: 200000,
      activeModel: 'claude-sonnet-4-6',
      activeProviderName: 'anthropic',
    }),
  );

  // 2. "Reload": the in-memory store + provider instance are gone; storage remains.
  const freshStore = createDefaultSessionState();
  const snapshot = await new WebStorageSessionProvider(storage).load(key);
  assert.ok(snapshot, 'snapshot survives the reload');
  assert.ok(isRestorableSession(snapshot, { host }), 'snapshot is restorable for this host');

  // 3. Restore into the fresh store (what maybeRestoreSession does).
  const restored = reduce(freshStore, loadSession(toLoadSessionPayload(snapshot!)));
  assert.is(restored.messages.length, 2);
  assert.is(restored.pinnedAgentName, 'Guided Booking');
  // Cost comes from the restored transcript, NOT the snapshot's stored counter — so it
  // is whatever the messages actually carry. Computed independently here rather than
  // by calling the summing helper under test.
  const expectedCost = restored.messages.reduce(
    (sum, m) => sum + (m.cost ?? 0) + (m.externalCostUsd ?? 0),
    0,
  );
  assert.is(restored.sessionUsage.costUsd, expectedCost);
  assert.is(restored.contextTokens, 800);
  assert.is(restored.contextLimit, 200000);
  assert.is(restored.activeModel, 'claude-sonnet-4-6');
  assert.is(restored.activeProviderName, 'anthropic');
  assert.is(restored.state, 'idle');
});

Suite('a cross-host snapshot is rejected on restore', async () => {
  const storage = fakeStorage();
  const provider = new WebStorageSessionProvider(storage);
  await provider.save(
    'k',
    createPersistedSession({
      sessionKey: 'k',
      host: 'app.example',
      messages: [asMsg({ role: 'user', content: 'x' })],
      pinnedAgentName: null,
      flowOwnerAgentName: null,
      sessionCostUsd: 0,
    }),
  );
  const snapshot = await provider.load('k');
  assert.ok(snapshot);
  assert.ok(!isRestorableSession(snapshot, { host: 'evil.example' }));
});

Suite('a dangling interaction tail is scrubbed before restore', async () => {
  const storage = fakeStorage();
  const provider = new WebStorageSessionProvider(storage);
  const messages = [
    asMsg({ role: 'user', content: 'hi' }),
    asMsg({
      role: 'assistant',
      content: '',
      interaction: { interactionId: '1', componentName: 'x', data: {} },
    }),
  ];
  await provider.save(
    'k',
    createPersistedSession({
      sessionKey: 'k',
      host: 'h',
      messages,
      pinnedAgentName: null,
      flowOwnerAgentName: null,
      sessionCostUsd: 0,
    }),
  );
  const snapshot = await provider.load('k');
  const scrubbed = trimToRestorableBoundary(snapshot!.messages);
  assert.is(scrubbed.length, 1, 'the unresolved interaction message is dropped');
});

Suite.run();
