// src/services/chat-session.ts
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
// All Rights Reserved

import env from "../config/env.js";

import path from "node:path";
import fs from "node:fs";

import {
  IChatSession,
  ChatSessionMode,
  GadgetId,
  IUser,
  IProject,
  ChatTurnStatus,
  ChatTurnDocument,
} from "@gadget/api";

import { DtpService } from "../lib/service.js";
import { PopulateOptions } from "mongoose";
import ChatSession from "../models/chat-session.js";
import ChatTurn from "../models/chat-turn.js";
import Project from "../models/project.js";
import AiProvider from "../models/ai-provider.js";

class ChatSessionService extends DtpService {
  private populateSession: PopulateOptions[] = [
    { path: "user", select: "-passwordSalt -password" },
    {
      path: "project",
      populate: [
        {
          path: "user",
          select: "-passwordSalt -password",
        },
      ],
    },
    { path: "provider" },
  ];
  private populateChatTurn: PopulateOptions[] = [
    {
      path: "user",
      select: "-passwordSalt -password",
    },
    {
      path: "project",
    },
    {
      path: "session",
      populate: this.populateSession,
    },
    {
      path: "provider",
      select: "-models +apiKey",
    },
  ];

  get name(): string {
    return "ChatSessionService";
  }
  get slug(): string {
    return "svc:chat-session";
  }

  async start(): Promise<void> {
    this.log.info("started");
  }

  async stop(): Promise<void> {
    this.log.info("stopped");
  }

  /**
   * Creates a new chat session.
   */
  async create(
    userId: GadgetId,
    projectId: GadgetId,
    providerId: GadgetId,
    selectedModel: string,
    mode: ChatSessionMode = ChatSessionMode.Build,
    name?: string,
  ): Promise<IChatSession> {
    // Validate project exists
    const project = await Project.findById(projectId);
    if (!project) {
      throw new Error(`Project not found: ${projectId}`);
    }

    // Validate provider exists
    const provider = await AiProvider.findById(providerId);
    if (!provider) {
      throw new Error(`AI Provider not found: ${providerId}`);
    }

    const session = new ChatSession({
      createdAt: new Date(),
      user: userId,
      project: projectId,
      name: name || "New Chat Session",
      mode,
      provider: providerId,
      selectedModel,
      stats: {
        turnCount: 0,
        toolCallCount: 0,
        inputTokens: 0,
        outputTokens: 0,
      },
      pins: [],
    });

    await session.save();

    this.log.info("chat session created", {
      sessionId: session._id,
      projectId,
      providerId,
      model: selectedModel,
    });

    return session.populate(this.populateSession);
  }

  /**
   * Gets a chat session by ID.
   */
  async getById(chatSessionId: GadgetId): Promise<IChatSession> {
    const session = await ChatSession.findById(chatSessionId)
      .populate("user", "-passwordSalt -password")
      .populate("project")
      .populate("provider")
      .lean();

    if (!session) {
      throw new Error(`Chat session not found: ${chatSessionId}`);
    }

    return session as unknown as IChatSession;
  }

  /**
   * Gets all chat sessions for a project.
   */
  async getByProject(projectId: GadgetId): Promise<IChatSession[]> {
    const sessions = await ChatSession.find({ project: projectId })
      .populate("user", "-passwordSalt -password")
      .populate("project")
      .populate("provider")
      .sort({ createdAt: -1 })
      .lean();

    return sessions as unknown as IChatSession[];
  }

  /**
   * Gets all chat sessions for a user.
   */
  async getByUser(userId: GadgetId): Promise<IChatSession[]> {
    const sessions = await ChatSession.find({ user: userId })
      .populate("user", "-passwordSalt -password")
      .populate("project")
      .populate("provider")
      .sort({ createdAt: -1 })
      .lean();

    return sessions as unknown as IChatSession[];
  }

  /**
   * Updates a chat session.
   */
  async update(
    chatSessionId: GadgetId,
    updates: Partial<{
      name: string;
      provider: GadgetId;
      selectedModel: string;
      mode: ChatSessionMode;
    }>,
  ): Promise<IChatSession> {
    const session = await ChatSession.findById(chatSessionId);
    if (!session) {
      throw new Error(`Chat session not found: ${chatSessionId}`);
    }

    // Validate provider if changing
    if (updates.provider) {
      const provider = await AiProvider.findById(updates.provider);
      if (!provider) {
        throw new Error(`AI Provider not found: ${updates.provider}`);
      }
      session.provider = updates.provider;
    }

    if (updates.name !== undefined) {
      session.name = updates.name;
    }
    if (updates.selectedModel !== undefined) {
      session.selectedModel = updates.selectedModel;
    }
    if (updates.mode !== undefined) {
      session.mode = updates.mode;
    }

    await session.save();

    this.log.info("chat session updated", {
      sessionId: chatSessionId,
      updates,
    });

    return session;
  }

  /**
   * Deletes a chat session.
   */
  async delete(chatSessionId: GadgetId): Promise<void> {
    const session = await ChatSession.findByIdAndDelete(chatSessionId);
    if (!session) {
      throw new Error(`Chat session not found: ${chatSessionId}`);
    }

    // Delete all turns associated with this session
    await ChatTurn.deleteMany({ session: chatSessionId });

    this.log.info("chat session deleted", {
      sessionId: chatSessionId,
    });
  }

  async createTurn(
    session: IChatSession,
    prompt: string,
  ): Promise<ChatTurnDocument> {
    const NOW = new Date();
    const user: IUser = session.user as IUser;
    const project: IProject = session.project as IProject;

    const systemPrompt = await this.buildSystemPrompt(session);

    let turn = new ChatTurn({
      createdAt: NOW,
      user: user._id,
      project: project._id,
      session: session._id,
      provider: session.provider,
      llm: session.selectedModel,
      mode: session.mode,
      status: ChatTurnStatus.Processing,
      prompts: {
        system: systemPrompt,
        user: prompt,
      },
      toolCalls: [],
      subagents: [],
      stats: {
        toolCallCount: 0,
        inputTokens: 0,
        thinkingTokenCount: 0,
        responseTokens: 0,
        durationMs: 0,
        durationLabel: "pending",
      },
    });

    await turn.save();
    turn = await ChatTurn.populate(turn, this.populateChatTurn);

    return turn;
  }

  async buildSystemPrompt(session: IChatSession): Promise<string> {
    const commonDir = path.join(env.installDir, "data", "prompts", "common");
    const promptsDir = path.join(
      env.installDir,
      "data",
      "prompts",
      "agent",
      session.mode,
    );

    const common = {
      scopeBlock: await fs.promises.readFile(
        path.join(commonDir, "scope-block.md"),
        "utf-8",
      ),
      subagentsBlock: await fs.promises.readFile(
        path.join(commonDir, "subagents.md"),
        "utf-8",
      ),
    };

    const templateFilename = path.join(promptsDir, "system.md");
    const promptTemplate = await fs.promises.readFile(
      templateFilename,
      "utf-8",
    );

    let prompt = promptTemplate
      .replace("{{scope_block}}", common.scopeBlock)
      .replace("{{subagent_section}}", common.subagentsBlock);

    return prompt;
  }

  /**
   * Gets all turns for a chat session.
   */
  async getTurns(chatSessionId: GadgetId): Promise<any[]> {
    const turns = await ChatTurn.find({
      session: chatSessionId,
      status: { $ne: ChatTurnStatus.Processing },
    })
      .populate("user", "-passwordSalt -password")
      .populate("project")
      .populate("provider", "-models")
      .sort({ createdAt: 1 })
      .lean();
    return turns;
  }

  /**
   * Generates a session name from the first prompt.
   */
  generateSessionNameFromPrompt(prompt: string): string {
    // Take first 50 chars, remove special chars, capitalize
    const truncated = prompt.slice(0, 50).replace(/[^a-zA-Z0-9\s]/g, "");
    const words = truncated.trim().split(/\s+/);
    const name = words.slice(0, 5).join(" ");
    return name.charAt(0).toUpperCase() + name.slice(1);
  }
}

export default new ChatSessionService();
