import type { GatewayRequest, OutboundMessage } from '../protocol.js';
import { makeResponse, makeError, ErrorCode } from '../protocol.js';
import type { SessionManager } from '../../session/manager.js';
import type { SessionBridge } from '../session-bridge.js';

export interface SessionHandlerDeps {
  sessionManager: SessionManager;
  sessionBridge?: SessionBridge;
}

const MAX_SESSION_LIST_LIMIT = 500;
const MAX_SESSION_HISTORY_LIMIT = 500;

function parsePositiveInt(value: unknown, field: string, max: number): { value: number } | { error: string } {
  if (value === undefined) {
    return { value: 0 };
  }
  if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {
    return { error: `${field} must be a non-negative integer` };
  }
  if (value > max) {
    return { error: `${field} must be <= ${max}` };
  }
  return { value };
}

export function createSessionHandlers(deps: SessionHandlerDeps) {
  return {
    'sessions.list': async (request: GatewayRequest): Promise<OutboundMessage> => {
      const params = request.params as {
        includePersisted?: boolean;
        frontend?: string;
        limit?: number;
        offset?: number;
      } | undefined;

      const parsedLimit = parsePositiveInt(params?.limit, 'limit', MAX_SESSION_LIST_LIMIT);
      if ('error' in parsedLimit) {
        return makeError(request.id, ErrorCode.InvalidRequest, parsedLimit.error);
      }
      const parsedOffset = parsePositiveInt(params?.offset, 'offset', Number.MAX_SAFE_INTEGER);
      if ('error' in parsedOffset) {
        return makeError(request.id, ErrorCode.InvalidRequest, parsedOffset.error);
      }
      if (params?.frontend !== undefined && (typeof params.frontend !== 'string' || !params.frontend.trim())) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'frontend must be a non-empty string');
      }

      const sessionIds = deps.sessionManager.listSessions({
        includePersisted: params?.includePersisted === true,
        frontend: params?.frontend,
      });

      const offset = parsedOffset.value;
      const end = parsedLimit.value > 0 ? offset + parsedLimit.value : undefined;
      const pagedSessionIds = sessionIds.slice(offset, end);

      const sessions = pagedSessionIds.map((id) => {
        const parts = id.split(':');
        const frontend = parts[0];
        const userId = parts.slice(1).join(':');
        const session = deps.sessionManager.getSession(frontend, userId);
        const history = session.getHistory();
        const lastMessageAt = history.length > 0 ? (history[history.length - 1].timestamp ?? undefined) : undefined;

        const modelTier = deps.sessionManager.getSessionConfig(frontend, userId, 'modelTier');
        const queueMode = deps.sessionManager.getSessionConfig(frontend, userId, 'queue.mode');
        const queueOverflow = deps.sessionManager.getSessionConfig(frontend, userId, 'queue.overflow');
        const queueCapRaw = deps.sessionManager.getSessionConfig(frontend, userId, 'queue.cap');
        const queueDebounceRaw = deps.sessionManager.getSessionConfig(frontend, userId, 'queue.debounce_ms');
        const queueSummarizeOverflowRaw = deps.sessionManager.getSessionConfig(frontend, userId, 'queue.summarize_overflow');
        const elevationUntilRaw = deps.sessionManager.getSessionConfig(frontend, userId, 'elevation.until_ms');
        const elevationReason = deps.sessionManager.getSessionConfig(frontend, userId, 'elevation.reason');

        const queueCap = queueCapRaw ? Number.parseInt(queueCapRaw, 10) : undefined;
        const queueDebounceMs = queueDebounceRaw ? Number.parseInt(queueDebounceRaw, 10) : undefined;
        const elevationUntilMs = elevationUntilRaw ? Number.parseInt(elevationUntilRaw, 10) : undefined;
        const queueSummarizeOverflow = queueSummarizeOverflowRaw === 'true'
          ? true
          : queueSummarizeOverflowRaw === 'false'
            ? false
            : undefined;

        return {
          id,
          frontend,
          userId,
          messageCount: history.length,
          lastMessageAt,
          config: {
            modelTier,
            queue: {
              mode: queueMode,
              overflow: queueOverflow,
              cap: Number.isFinite(queueCap) ? queueCap : undefined,
              debounceMs: Number.isFinite(queueDebounceMs) ? queueDebounceMs : undefined,
              summarizeOverflow: queueSummarizeOverflow,
            },
            elevation: {
              active: Number.isFinite(elevationUntilMs) ? Number(elevationUntilMs) > Date.now() : false,
              untilMs: Number.isFinite(elevationUntilMs) ? elevationUntilMs : undefined,
              reason: elevationReason || undefined,
            },
          },
        };
      });
      return makeResponse(request.id, {
        sessions,
        total: sessionIds.length,
      });
    },

    'sessions.history': async (request: GatewayRequest): Promise<OutboundMessage> => {
      const params = request.params as { sessionId?: string; limit?: number; offset?: number } | undefined;
      if (!params?.sessionId) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'sessionId is required');
      }

      const parsedLimit = parsePositiveInt(params.limit, 'limit', MAX_SESSION_HISTORY_LIMIT);
      if ('error' in parsedLimit) {
        return makeError(request.id, ErrorCode.InvalidRequest, parsedLimit.error);
      }
      const parsedOffset = parsePositiveInt(params.offset, 'offset', Number.MAX_SAFE_INTEGER);
      if ('error' in parsedOffset) {
        return makeError(request.id, ErrorCode.InvalidRequest, parsedOffset.error);
      }

      const { sessionId } = params;
      const parts = sessionId.split(':');
      const frontend = parts[0];
      const userId = parts.slice(1).join(':');
      const session = deps.sessionManager.getSession(frontend, userId);
      const allMessages = session.getHistory();

      const start = parsedOffset.value;
      const end = parsedLimit.value > 0 ? start + parsedLimit.value : allMessages.length;
      const messages = allMessages.slice(start, end);

      return makeResponse(request.id, {
        messages,
        total: allMessages.length,
      });
    },

    'sessions.create': async (request: GatewayRequest): Promise<OutboundMessage> => {
      const params = request.params as { sessionId?: string } | undefined;
      const sessionId = params?.sessionId ?? `ws:${Date.now()}`;
      const parts = sessionId.split(':');
      const frontend = parts[0];
      const userId = parts.slice(1).join(':');

      // Creating a session via getSession is idempotent
      deps.sessionManager.getSession(frontend, userId);

      return makeResponse(request.id, { sessionId });
    },

    'sessions.delete': async (request: GatewayRequest): Promise<OutboundMessage> => {
      const params = request.params as { sessionId?: string } | undefined;
      if (!params?.sessionId) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'sessionId is required');
      }

      const { sessionId } = params;
      const parts = sessionId.split(':');
      const frontend = parts[0];
      const userId = parts.slice(1).join(':');
      const session = deps.sessionManager.getSession(frontend, userId);
      session.clear();

      return makeResponse(request.id, { deleted: true, sessionId });
    },

    'sessions.switch': async (request: GatewayRequest): Promise<OutboundMessage> => {
      const params = request.params as { sessionId?: string; connectionId?: string } | undefined;
      if (!params?.sessionId) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'sessionId is required');
      }
      if (!deps.sessionBridge) {
        return makeError(request.id, ErrorCode.InternalError, 'Session switching not available');
      }

      const connectionId = params.connectionId as string;
      if (!connectionId) {
        return makeError(request.id, ErrorCode.InvalidRequest, 'connectionId is required');
      }

      try {
        deps.sessionBridge.switchSession(connectionId, params.sessionId);
        return makeResponse(request.id, { switched: true, sessionId: params.sessionId });
      } catch (err) {
        const message = err instanceof Error ? err.message : 'Failed to switch session';
        return makeError(request.id, ErrorCode.InternalError, message);
      }
    },
  };
}
