import path from "path";
import { ASSISTANT_SESSIONS_DIR } from "../config";
import type { AssistantConversationItem, AssistantReplyType, AssistantSessionState } from "../types/assistant";
import { ensureDir, writeJsonFile } from "../utils/files";

interface AssistantTurnLogRecord {
  turn_id: string;
  started_at: string | null;
  completed_at: string | null;
  human_block: string;
  human_readable: {
    question_raw: string;
    question_understood: string;
    decomposition: string[];
    answer: string;
    reply_type: AssistantReplyType | null;
  };
  technical_json: {
    trace_id: string | null;
    user_message: AssistantConversationItem;
    assistant_message: AssistantConversationItem;
    debug: AssistantConversationItem["debug"];
  };
}

interface AssistantSessionLogRecord {
  schema_version: "assistant_session_log_v1";
  session_id: string;
  started_at: string;
  updated_at: string;
  counters: {
    total_messages: number;
    user_messages: number;
    assistant_messages: number;
  };
  trace_ids: string[];
  reply_types: AssistantReplyType[];
  investigation_state: AssistantSessionState["investigation_state"];
  turns: AssistantTurnLogRecord[];
  conversation: AssistantConversationItem[];
  last_assistant: {
    message_id: string | null;
    reply_type: AssistantReplyType | null;
    trace_id: string | null;
    created_at: string | null;
  };
}

function unique(values: Array<string | null>): string[] {
  return Array.from(new Set(values.filter((item): item is string => typeof item === "string" && item.length > 0)));
}

function toObject(value: unknown): Record<string, unknown> | null {
  if (!value || typeof value !== "object" || Array.isArray(value)) {
    return null;
  }
  return value as Record<string, unknown>;
}

function toStringOrNull(value: unknown): string | null {
  if (typeof value !== "string") {
    return null;
  }
  const trimmed = value.trim();
  return trimmed ? trimmed : null;
}

function extractFragments(assistantItem: AssistantConversationItem): Array<Record<string, unknown>> {
  if (!assistantItem.debug || !Array.isArray(assistantItem.debug.fragments)) {
    return [];
  }
  return assistantItem.debug.fragments
    .map((item) => toObject(item))
    .filter((item): item is Record<string, unknown> => item !== null);
}

function extractNormalizedQuestion(userText: string, assistantItem: AssistantConversationItem): string {
  const normalized = toObject(assistantItem.debug?.normalized);
  if (normalized) {
    const fromUserMessageRaw = toStringOrNull(normalized.user_message_raw);
    if (fromUserMessageRaw) return fromUserMessageRaw;
    const fromUserQuestionRaw = toStringOrNull(normalized.user_question_raw);
    if (fromUserQuestionRaw) return fromUserQuestionRaw;
    const fromNormalizedQuestion = toStringOrNull(normalized.normalized_question);
    if (fromNormalizedQuestion) return fromNormalizedQuestion;
  }

  const fragments = extractFragments(assistantItem);
  if (fragments.length > 0) {
    const joined = fragments
      .map((fragment) => toStringOrNull(fragment.normalized_fragment_text) ?? toStringOrNull(fragment.raw_fragment_text))
      .filter((item): item is string => Boolean(item))
      .join(" | ");
    if (joined) {
      return joined;
    }
  }

  return userText;
}

function buildRouteLookup(assistantItem: AssistantConversationItem): Map<string, Record<string, unknown>> {
  const output = new Map<string, Record<string, unknown>>();
  if (!assistantItem.debug || !Array.isArray(assistantItem.debug.routes)) {
    return output;
  }
  for (const route of assistantItem.debug.routes) {
    const routeObject = toObject(route);
    if (!routeObject) continue;
    const fragmentId = toStringOrNull(routeObject.fragment_id);
    if (!fragmentId) continue;
    output.set(fragmentId, routeObject);
  }
  return output;
}

function buildDecompositionLines(assistantItem: AssistantConversationItem): string[] {
  const fragments = extractFragments(assistantItem);
  if (fragments.length === 0) {
    return ["Фрагменты декомпозиции не выделены."];
  }

  const routeLookup = buildRouteLookup(assistantItem);

  return fragments.map((fragment, index) => {
    const fragmentId = toStringOrNull(fragment.fragment_id) ?? `F${index + 1}`;
    const fragmentText =
      toStringOrNull(fragment.normalized_fragment_text) ??
      toStringOrNull(fragment.raw_fragment_text) ??
      "текст фрагмента отсутствует";
    const executionReadiness = toStringOrNull(fragment.execution_readiness);
    const routeStatus = toStringOrNull(fragment.route_status);

    const routeObject = routeLookup.get(fragmentId);
    const route = toStringOrNull(routeObject?.route);
    const noRouteReason =
      toStringOrNull(fragment.no_route_reason) ?? toStringOrNull(routeObject?.no_route_reason);

    const parts = [`${fragmentId}: ${fragmentText}`];
    if (executionReadiness) parts.push(`execution_readiness=${executionReadiness}`);
    if (routeStatus) parts.push(`route_status=${routeStatus}`);
    if (route) parts.push(`route=${route}`);
    if (noRouteReason) parts.push(`no_route_reason=${noRouteReason}`);

    return parts.join("; ");
  });
}

function toHumanBlock(input: {
  questionRaw: string;
  questionUnderstood: string;
  decomposition: string[];
  answer: string;
}): string {
  const lines: string[] = [];
  lines.push(`Вопрос: ${input.questionRaw}`);
  lines.push(`Понято как: ${input.questionUnderstood}`);
  lines.push("Декомпозиция:");
  lines.push(...input.decomposition.map((item) => `- ${item}`));
  lines.push(`Ответ: ${input.answer}`);
  return lines.join("\n");
}

function buildTurns(items: AssistantConversationItem[]): AssistantTurnLogRecord[] {
  const turns: AssistantTurnLogRecord[] = [];
  const pendingUsers: AssistantConversationItem[] = [];

  for (const item of items) {
    if (item.role === "user") {
      pendingUsers.push(item);
      continue;
    }

    const pairedUser = pendingUsers.shift();
    if (!pairedUser) {
      continue;
    }

    const questionRaw = pairedUser.text;
    const questionUnderstood = extractNormalizedQuestion(questionRaw, item);
    const decomposition = buildDecompositionLines(item);
    const answer = item.text;

    turns.push({
      turn_id: `turn-${turns.length + 1}`,
      started_at: pairedUser.created_at ?? null,
      completed_at: item.created_at ?? null,
      human_block: toHumanBlock({
        questionRaw,
        questionUnderstood,
        decomposition,
        answer
      }),
      human_readable: {
        question_raw: questionRaw,
        question_understood: questionUnderstood,
        decomposition,
        answer,
        reply_type: item.reply_type
      },
      technical_json: {
        trace_id: item.trace_id,
        user_message: pairedUser,
        assistant_message: item,
        debug: item.debug
      }
    });
  }

  return turns;
}

export class AssistantSessionLogger {
  constructor(private readonly rootDir: string = ASSISTANT_SESSIONS_DIR) {}

  public persistSession(session: AssistantSessionState): void {
    ensureDir(this.rootDir);
    const filePath = path.resolve(this.rootDir, `${session.session_id}.json`);

    const startedAt = session.items[0]?.created_at ?? session.updated_at;
    const userMessages = session.items.filter((item) => item.role === "user").length;
    const assistantMessages = session.items.filter((item) => item.role === "assistant").length;
    const assistantItems = session.items.filter((item) => item.role === "assistant");
    const lastAssistant = assistantItems.length > 0 ? assistantItems[assistantItems.length - 1] : null;

    const traceIds = unique(session.items.map((item) => item.trace_id));
    const replyTypes = Array.from(
      new Set(
        session.items
          .map((item) => item.reply_type)
          .filter((item): item is AssistantReplyType => typeof item === "string" && item.length > 0)
      )
    );
    const turns = buildTurns(session.items);

    const record: AssistantSessionLogRecord = {
      schema_version: "assistant_session_log_v1",
      session_id: session.session_id,
      started_at: startedAt,
      updated_at: session.updated_at,
      counters: {
        total_messages: session.items.length,
        user_messages: userMessages,
        assistant_messages: assistantMessages
      },
      trace_ids: traceIds,
      reply_types: replyTypes,
      investigation_state: session.investigation_state,
      turns,
      conversation: session.items,
      last_assistant: {
        message_id: lastAssistant?.message_id ?? null,
        reply_type: lastAssistant?.reply_type ?? null,
        trace_id: lastAssistant?.trace_id ?? null,
        created_at: lastAssistant?.created_at ?? null
      }
    };

    writeJsonFile(filePath, record);
  }
}
