import type {
  ChatDocumentAttachment,
  ChatImageAttachment,
  ChatTimelineItem,
  ChatTimelineMessageItem,
} from '@easyflow/common-ui';

import type { AgentChatCapabilityPayload } from './api';

import { ChatTimelineBuilder } from '@easyflow/common-ui';
import { useUserStore } from '@easyflow/stores';

import {
  onAgentChatCacheClear,
  resolveAgentChatIdentity,
  RUNTIME_STORAGE_PREFIX,
} from '#/utils/agent-chat-cache';

import {
  applyAgentSseEnvelope,
  parseAgentSseMessage,
} from './adapters/agentTimelineAdapter';
import {
  generateAgentSessionId,
  sendAgentChat,
  stopAgentChatStream,
} from './api';

interface RuntimeSessionState {
  agentId: string;
  agentName?: string;
  completed: boolean;
  error?: string;
  identity: string;
  items: ChatTimelineItem[];
  prompt: string;
  roundId: string;
  sending: boolean;
  sessionId: string;
  updatedAt: number;
}

interface StoredRuntimeSession {
  agentId: string;
  agentName?: string;
  completed: boolean;
  error?: string;
  items: ChatTimelineItem[];
  prompt: string;
  roundId: string;
  sessionId: string;
  updatedAt: number;
  version: number;
}

interface StartOptions {
  agentId: string;
  agentName?: string;
  baseItems?: ChatTimelineItem[];
  capabilities?: AgentChatCapabilityPayload[];
  documentUploadIds?: string[];
  documents?: ChatDocumentAttachment[];
  imageUploadIds?: string[];
  images?: ChatImageAttachment[];
  onInputAccepted?: () => Promise<void> | void;
  prompt: string;
  sessionId?: string;
}

const STORAGE_VERSION = 2;
const STREAM_NOTIFY_INTERVAL_MS = 50;
const STREAM_PERSIST_INTERVAL_MS = 300;
const MAX_RUNTIME_SESSIONS_PER_IDENTITY = 10;

const sessions = new Map<string, RuntimeSessionState>();
const listeners = new Set<() => void>();
const latestSessionIds = new Map<string, string>();
const persistTimers = new Map<string, ReturnType<typeof setTimeout>>();
let notifyTimer: ReturnType<typeof setTimeout> | undefined;

function clone<T>(value: T): T {
  const serialized = JSON.stringify(value);
  return JSON.parse(serialized) as T;
}

function createRoundId() {
  return `agent-chat-round-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
}

function identityScope() {
  return resolveAgentChatIdentity(useUserStore().userInfo);
}

function sessionKey(identity: string, sessionId: string) {
  return `${identity}:${sessionId}`;
}

function storageKey(identity: string, sessionId: string) {
  return `${RUNTIME_STORAGE_PREFIX}:${identity}:${sessionId}`;
}

function latestStorageKey(identity: string) {
  return `${RUNTIME_STORAGE_PREFIX}:${identity}:latest`;
}

function safeSessionStorage() {
  try {
    return globalThis.sessionStorage;
  } catch {
    return undefined;
  }
}

function notify() {
  for (const listener of listeners) {
    listener();
  }
}

function notifyNow() {
  if (notifyTimer) {
    clearTimeout(notifyTimer);
    notifyTimer = undefined;
  }
  notify();
}

function scheduleNotify() {
  if (notifyTimer || listeners.size === 0) {
    return;
  }
  notifyTimer = setTimeout(() => {
    notifyTimer = undefined;
    notify();
  }, STREAM_NOTIFY_INTERVAL_MS);
}

function persistSession(state: RuntimeSessionState) {
  const storage = safeSessionStorage();
  if (!storage) {
    return;
  }
  const snapshot: StoredRuntimeSession = {
    agentId: state.agentId,
    agentName: state.agentName,
    completed: state.completed,
    error: state.error,
    items: state.items,
    prompt: state.prompt,
    roundId: state.roundId,
    sessionId: state.sessionId,
    updatedAt: state.updatedAt,
    version: STORAGE_VERSION,
  };
  try {
    storage.setItem(
      storageKey(state.identity, state.sessionId),
      JSON.stringify(snapshot),
    );
    storage.setItem(latestStorageKey(state.identity), state.sessionId);
  } catch {
    // 缓存失败不影响正式聊天主流程。
  }
}

function cancelPersistTimer(scopedSessionKey: string) {
  const timer = persistTimers.get(scopedSessionKey);
  if (!timer) {
    return;
  }
  clearTimeout(timer);
  persistTimers.delete(scopedSessionKey);
}

function removeSessionCache(
  scopedSessionKey: string,
  state: RuntimeSessionState,
) {
  cancelPersistTimer(scopedSessionKey);
  sessions.delete(scopedSessionKey);
  try {
    safeSessionStorage()?.removeItem(
      storageKey(state.identity, state.sessionId),
    );
  } catch {
    // 本地缓存清理失败不影响服务端会话。
  }
}

function pruneRuntimeSessions(identity: string, currentSessionId: string) {
  const scopedSessions = [...sessions.entries()]
    .filter(([, session]) => session.identity === identity)
    .sort((first, second) => second[1].updatedAt - first[1].updatedAt);
  if (scopedSessions.length <= MAX_RUNTIME_SESSIONS_PER_IDENTITY) {
    return;
  }
  const protectedKeys = new Set(
    scopedSessions
      .filter(
        ([, session]) =>
          session.sending || session.sessionId === currentSessionId,
      )
      .map(([key]) => key),
  );
  let retainedCount = protectedKeys.size;
  for (const [key, session] of scopedSessions) {
    if (protectedKeys.has(key)) {
      continue;
    }
    if (retainedCount < MAX_RUNTIME_SESSIONS_PER_IDENTITY) {
      retainedCount += 1;
      continue;
    }
    removeSessionCache(key, session);
  }
}

function touchState(state: RuntimeSessionState) {
  state.updatedAt = Date.now();
  latestSessionIds.set(state.identity, state.sessionId);
  sessions.set(sessionKey(state.identity, state.sessionId), state);
}

function scheduleStateUpdate(state: RuntimeSessionState) {
  touchState(state);
  const scopedSessionKey = sessionKey(state.identity, state.sessionId);
  if (!persistTimers.has(scopedSessionKey)) {
    persistTimers.set(
      scopedSessionKey,
      setTimeout(() => {
        persistTimers.delete(scopedSessionKey);
        const current = sessions.get(scopedSessionKey);
        if (current) {
          persistSession(current);
        }
      }, STREAM_PERSIST_INTERVAL_MS),
    );
  }
  scheduleNotify();
}

function restoreSession(identity: string, sessionId: string) {
  if (!identity) {
    return undefined;
  }
  const scopedSessionKey = sessionKey(identity, sessionId);
  const existing = sessions.get(scopedSessionKey);
  if (existing) {
    return existing;
  }
  const storage = safeSessionStorage();
  if (!storage) {
    return undefined;
  }
  try {
    const raw = storage.getItem(storageKey(identity, sessionId));
    if (!raw) {
      return undefined;
    }
    const parsed = JSON.parse(raw) as StoredRuntimeSession;
    if (parsed.version !== STORAGE_VERSION || parsed.sessionId !== sessionId) {
      return undefined;
    }
    const restored: RuntimeSessionState = {
      agentId: parsed.agentId,
      agentName: parsed.agentName,
      completed: parsed.completed,
      error: parsed.error,
      identity,
      items: Array.isArray(parsed.items) ? parsed.items : [],
      prompt: parsed.prompt,
      roundId: parsed.roundId,
      sending: false,
      sessionId,
      updatedAt: parsed.updatedAt,
    };
    sessions.set(scopedSessionKey, restored);
    return restored;
  } catch {
    return undefined;
  }
}

function upsertState(state: RuntimeSessionState) {
  touchState(state);
  cancelPersistTimer(sessionKey(state.identity, state.sessionId));
  persistSession(state);
  pruneRuntimeSessions(state.identity, state.sessionId);
  notifyNow();
}

function runningSession(identity = identityScope()) {
  return [...sessions.values()].find(
    (session) => session.identity === identity && session.sending,
  );
}

function restoreLatestSession(identity = identityScope()) {
  if (!identity) {
    return undefined;
  }
  const running = runningSession(identity);
  if (running) {
    return running;
  }
  const storage = safeSessionStorage();
  const storedSessionId = storage?.getItem(latestStorageKey(identity)) || '';
  const sessionId = latestSessionIds.get(identity) || storedSessionId;
  return sessionId ? restoreSession(identity, sessionId) : undefined;
}

async function resolveSessionId(sessionId?: string) {
  if (sessionId) {
    return sessionId;
  }
  const res = await generateAgentSessionId();
  if (res.errorCode !== 0 || !res.data) {
    throw new Error(res.message || '会话创建失败');
  }
  return String(res.data);
}

function errorMessage(error: unknown) {
  return error instanceof Error ? error.message : '发送失败，请稍后再试';
}

function normalizeAcceptedImages(payload: Record<string, any>) {
  if (!Array.isArray(payload.images)) {
    return [];
  }
  return payload.images
    .map((value): ChatImageAttachment | undefined => {
      if (!value || typeof value !== 'object' || Array.isArray(value)) {
        return undefined;
      }
      const image = value as Record<string, any>;
      const previewUrl = String(image.previewUrl || '');
      if (!previewUrl) {
        return undefined;
      }
      return {
        height: Number(image.height || 0) || undefined,
        imageRef: String(image.imageRef || ''),
        mimeType: String(image.mimeType || ''),
        name: String(image.name || '图片'),
        previewUrl,
        size: Number(image.size || 0) || undefined,
        status: 'ready',
        width: Number(image.width || 0) || undefined,
      };
    })
    .filter((item): item is ChatImageAttachment => item !== undefined);
}

function normalizeAcceptedDocuments(payload: Record<string, any>) {
  if (!Array.isArray(payload.attachments)) {
    return [];
  }
  return payload.attachments
    .map((value): ChatDocumentAttachment | undefined => {
      if (!value || typeof value !== 'object' || Array.isArray(value)) {
        return undefined;
      }
      const document = value as Record<string, any>;
      const attachmentRef = String(document.attachmentRef || '');
      if (!attachmentRef) {
        return undefined;
      }
      return {
        attachmentRef,
        downloadUrl: String(document.downloadUrl || ''),
        mimeType: String(document.mimeType || ''),
        name: String(document.name || '文档'),
        readSnapshotId: String(document.readSnapshotId || ''),
        size: Number(document.size || 0) || undefined,
        status: 'ready',
      };
    })
    .filter((item): item is ChatDocumentAttachment => item !== undefined);
}

function replaceAcceptedAttachments(
  items: ChatTimelineItem[],
  roundId: string,
  payload: Record<string, any>,
) {
  const acceptedImages = normalizeAcceptedImages(payload);
  const acceptedDocuments = normalizeAcceptedDocuments(payload);
  if (acceptedImages.length === 0 && acceptedDocuments.length === 0) {
    return;
  }
  const userMessage = items.find(
    (item): item is ChatTimelineMessageItem =>
      item.type === 'message' &&
      item.role === 'user' &&
      item.roundId === roundId,
  );
  if (userMessage) {
    if (acceptedImages.length > 0) {
      userMessage.images = acceptedImages;
    }
    if (acceptedDocuments.length > 0) {
      userMessage.documents = acceptedDocuments;
    }
  }
}

onAgentChatCacheClear((identity) => {
  for (const [key, session] of sessions) {
    if (session.identity === identity) {
      cancelPersistTimer(key);
      sessions.delete(key);
    }
  }
  latestSessionIds.delete(identity);
  notifyNow();
});

export const agentChatRuntimeManager = {
  getSnapshot(sessionId?: string) {
    if (!sessionId) {
      return undefined;
    }
    const state = restoreSession(identityScope(), sessionId);
    return state ? clone(state) : undefined;
  },

  getLatestSnapshot() {
    const state = restoreLatestSession();
    return state ? clone(state) : undefined;
  },

  hasRunning() {
    return Boolean(runningSession());
  },

  replaceItems(sessionId: string, items: ChatTimelineItem[]) {
    const state = restoreSession(identityScope(), sessionId);
    if (!state) {
      return;
    }
    state.items = clone(items);
    upsertState(state);
  },

  async start(options: StartOptions) {
    const identity = identityScope();
    if (!identity) {
      throw new Error('当前登录状态失效');
    }
    const active = runningSession(identity);
    if (active) {
      throw new Error('当前回复完成后再发送新消息');
    }
    const sessionId = await resolveSessionId(options.sessionId);
    const roundId = createRoundId();
    const state: RuntimeSessionState = {
      agentId: options.agentId,
      agentName: options.agentName,
      completed: false,
      identity,
      items: clone(options.baseItems || []),
      prompt: options.prompt,
      roundId,
      sending: true,
      sessionId,
      updatedAt: Date.now(),
    };
    ChatTimelineBuilder.appendUserMessage(state.items, options.prompt, {
      documents: options.documents,
      images: options.images,
      roundId,
    });
    upsertState(state);

    void sendAgentChat(
      {
        agentId: options.agentId,
        capabilities: options.capabilities,
        documentUploadIds: options.documentUploadIds,
        imageUploadIds: options.imageUploadIds,
        prompt: options.prompt,
        sessionId,
      },
      {
        onError(error) {
          const current = sessions.get(sessionKey(identity, sessionId));
          if (!current || !current.sending) {
            return;
          }
          current.error = errorMessage(error);
          current.sending = false;
          current.completed = true;
          ChatTimelineBuilder.appendError(current.items, current.error);
          ChatTimelineBuilder.finalize(current.items);
          upsertState(current);
        },
        onFinished() {
          const current = sessions.get(sessionKey(identity, sessionId));
          if (!current) {
            return;
          }
          current.sending = false;
          current.completed = true;
          ChatTimelineBuilder.finalize(current.items);
          upsertState(current);
        },
        onMessage(message) {
          const current = sessions.get(sessionKey(identity, sessionId));
          if (!current || !current.sending) {
            return;
          }
          const envelope = parseAgentSseMessage(message);
          if (!envelope) {
            return;
          }
          if (
            envelope.domain === 'SYSTEM' &&
            envelope.type === 'INPUT_ACCEPTED'
          ) {
            replaceAcceptedAttachments(
              current.items,
              roundId,
              envelope.payload,
            );
            void options.onInputAccepted?.();
          }
          applyAgentSseEnvelope(current.items, envelope, { roundId });
          scheduleStateUpdate(current);
        },
      },
    );

    return sessionId;
  },

  stop(sessionId?: string) {
    const identity = identityScope();
    const state = sessionId
      ? restoreSession(identity, sessionId)
      : runningSession(identity);
    if (!state || !state.sending) {
      return;
    }
    stopAgentChatStream();
    state.sending = false;
    state.completed = true;
    ChatTimelineBuilder.finalize(state.items);
    upsertState(state);
  },

  subscribe(listener: () => void) {
    listeners.add(listener);
    return () => {
      listeners.delete(listener);
    };
  },
};
