// src/services/session.ts
// Copyright (C) 2026 DTP Technologies, LLC
// All Rights Reserved

import crypto from "crypto";
import dayjs from "dayjs";

import { DtpService } from "../lib/service.js";
import Session, { ISession } from "../models/session.js";
import User from "../models/user.js";
import ChatSession from "../models/chat-session.js";

export interface SessionOptions {
  expiresHours: number;
  maxInactiveHours: number;
}

export class SessionService extends DtpService {
  get name(): string {
    return "SessionService";
  }
  get slug(): string {
    return "session";
  }

  private defaultExpiresHours = 24;

  constructor() {
    super();
  }

  async start(): Promise<void> {
    this.log.info("service started");
    // Start session cleanup cron job
    this.startCleanupJob();
  }

  async stop(): Promise<void> {
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
      this.cleanupInterval = null;
    }
    this.log.info("service stopped");
  }

  private cleanupInterval: NodeJS.Timeout | null = null;

  private startCleanupJob(): void {
    // Clean up expired sessions every hour
    this.cleanupInterval = setInterval(
      async () => {
        try {
          const result = await Session.deleteMany({
            expiresAt: { $lt: new Date() },
          });
          if (result.deletedCount > 0) {
            this.log.info(`Cleaned up ${result.deletedCount} expired sessions`);
          }
        } catch (error) {
          this.log.error("Session cleanup failed", { error });
        }
      },
      60 * 60 * 1000,
    ); // 1 hour
  }

  async create(
    userId: string,
    ipAddress?: string,
    userAgent?: string,
    options?: SessionOptions,
  ): Promise<ISession> {
    const NOW = new Date();
    const expiresHours = options?.expiresHours || this.defaultExpiresHours;

    const session = new Session();
    session.userId = new (await import("mongoose")).Types.ObjectId(userId);
    session.token = crypto.randomBytes(64).toString("hex");
    session.createdAt = NOW;
    session.expiresAt = dayjs(NOW).add(expiresHours, "hour").toDate();
    session.lastActivityAt = NOW;
    session.ipAddress = ipAddress;
    session.userAgent = userAgent;

    await session.save();
    this.log.debug("Session created", { userId, sessionId: session._id });

    return session;
  }

  async findById(sessionId: string): Promise<ISession | null> {
    const session = await Session.findById(sessionId).populate("userId");
    if (!session) {
      this.log.debug("Session not found", { sessionId });
      return null;
    }

    // Check if session is expired
    if (session.expiresAt < new Date()) {
      this.log.debug("Session expired", { sessionId: session._id });
      await Session.deleteOne({ _id: session._id });
      return null;
    }

    // Update last activity
    session.lastActivityAt = new Date();
    await session.save();

    this.log.debug("Session found", { sessionId: session._id });
    return session;
  }

  async findByToken(token: string): Promise<ISession | null> {
    const session = await Session.findOne({ token }).populate("userId");
    if (!session) {
      return null;
    }

    // Check if session is expired
    if (session.expiresAt < new Date()) {
      this.log.debug("Session expired", { sessionId: session._id });
      await Session.deleteOne({ _id: session._id });
      return null;
    }

    // Update last activity
    session.lastActivityAt = new Date();
    await session.save();

    return session;
  }

  async revoke(token: string): Promise<boolean> {
    const result = await Session.deleteOne({ token });
    if (result.deletedCount > 0) {
      this.log.debug("Session revoked", { token });
      return true;
    }
    return false;
  }

  async revokeAllForUser(userId: string): Promise<number> {
    const result = await Session.deleteMany({ userId });
    this.log.debug("Revoked all sessions for user", {
      userId,
      count: result.deletedCount,
    });
    return result.deletedCount;
  }

  async extend(session: ISession, hours?: number): Promise<ISession> {
    const extendHours = hours || this.defaultExpiresHours;
    session.expiresAt = dayjs(session.expiresAt)
      .add(extendHours, "hour")
      .toDate();
    session.lastActivityAt = new Date();
    await session.save();
    this.log.debug("Session extended", { sessionId: session._id });
    return session;
  }

  async getUserFromSession(session: ISession) {
    if (!session.userId) {
      return null;
    }
    // userId is already populated from findByToken
    return session.userId as unknown as typeof User.prototype;
  }

  async listByProject(
    projectId: string,
    userId: string,
  ): Promise<
    Array<{
      _id: string;
      name: string;
      lastMessageAt: Date | null;
      turnCount: number;
      toolCallCount: number;
      createdAt: Date;
    }>
  > {
    const sessions = await ChatSession.find({
      user: userId,
      project: projectId,
    })
      .sort({ lastMessageAt: -1, createdAt: -1 })
      .lean();

    return sessions.map((s) => ({
      _id: s._id.toString(),
      name: s.name,
      lastMessageAt: s.lastMessageAt ?? null,
      turnCount: s.stats.turnCount,
      toolCallCount: s.stats.toolCallCount,
      createdAt: s.createdAt,
    }));
  }
}

export default new SessionService();
