'use strict';

const crypto = require('node:crypto');
const fs = require('node:fs');
const path = require('node:path');

function createSessionId(prefix = 'agent-session') {
  return `${prefix}-${crypto.randomUUID()}`;
}

function nowIso() {
  return new Date().toISOString();
}

function clone(value) {
  return JSON.parse(JSON.stringify(value));
}

class AgentSessionStore {
  constructor({ persistencePath = null, clock = nowIso } = {}) {
    this.persistencePath = persistencePath;
    this.clock = clock;
    this.sessions = new Map();
    this._load();
  }

  createSession(data = {}) {
    const timestamp = this.clock();
    const id = data.id || data.sessionId || createSessionId();
    const session = {
      id,
      parentRunId: data.parentRunId || null,
      runtime: data.runtime || data.kind || 'cli-agent',
      runnerId: data.runnerId || data.runner || 'unknown',
      providerType: data.providerType || data.provider || 'unknown',
      model: data.model || null,
      mode: data.mode || 'build',
      cwd: data.cwd || process.cwd(),
      sandbox: data.sandbox || null,
      task: data.task || data.prompt || '',
      status: data.status || 'created',
      persistent: Boolean(data.persistent),
      cleanupPolicy: data.cleanupPolicy || (data.persistent ? 'manual' : 'delete-on-complete'),
      threadId: data.threadId || null,
      resumeHandle: data.resumeHandle || data.resumeToken || id,
      createdAt: data.createdAt || timestamp,
      updatedAt: data.updatedAt || timestamp,
      startedAt: data.startedAt || null,
      completedAt: data.completedAt || null,
      lastError: data.lastError || null,
      metadata: { ...(data.metadata || {}) },
    };
    this.sessions.set(id, session);
    this._persist();
    return clone(session);
  }

  getSession(id) {
    if (!id) return null;
    const session = this.sessions.get(id);
    return session ? clone(session) : null;
  }

  listSessions(filter = {}) {
    return [...this.sessions.values()]
      .filter((session) => {
        if (filter.parentRunId && session.parentRunId !== filter.parentRunId) return false;
        if (filter.runnerId && session.runnerId !== filter.runnerId) return false;
        if (filter.status && session.status !== filter.status) return false;
        return true;
      })
      .map(clone);
  }

  updateSession(id, patch = {}) {
    const session = this.sessions.get(id);
    if (!session) return null;
    const next = {
      ...session,
      ...patch,
      metadata: {
        ...(session.metadata || {}),
        ...(patch.metadata || {}),
      },
      updatedAt: this.clock(),
    };
    this.sessions.set(id, next);
    this._persist();
    return clone(next);
  }

  markRunning(id, patch = {}) {
    return this.updateSession(id, {
      ...patch,
      status: 'running',
      startedAt: patch.startedAt || this.clock(),
      lastError: null,
    });
  }

  markCompleted(id, result = {}) {
    return this.updateSession(id, {
      status: result.success === false ? 'failed' : 'completed',
      completedAt: this.clock(),
      resumeHandle: result.resumeHandle || result.sessionId || id,
      lastError: result.success === false
        ? String(result.error || result.stderr || 'Agent run failed')
        : null,
      metadata: {
        exitCode: result.exitCode,
        changedFiles: Array.isArray(result.changedFiles) ? result.changedFiles : [],
        timedOut: Boolean(result.timedOut),
      },
    });
  }

  markFailed(id, error) {
    return this.updateSession(id, {
      status: 'failed',
      completedAt: this.clock(),
      lastError: error?.message || String(error || 'Agent run failed'),
    });
  }

  deleteSession(id) {
    const existed = this.sessions.delete(id);
    if (existed) this._persist();
    return existed;
  }

  cleanupCompleted({ includePersistent = false } = {}) {
    let count = 0;
    for (const session of this.sessions.values()) {
      const terminal = session.status === 'completed' || session.status === 'failed' || session.status === 'canceled';
      const cleanup = session.cleanupPolicy === 'delete-on-complete' || (includePersistent && session.persistent);
      if (terminal && cleanup) {
        this.sessions.delete(session.id);
        count += 1;
      }
    }
    if (count > 0) this._persist();
    return count;
  }

  clear() {
    this.sessions.clear();
    this._persist();
  }

  _load() {
    if (!this.persistencePath) return;
    try {
      const raw = fs.readFileSync(this.persistencePath, 'utf8');
      const parsed = JSON.parse(raw);
      for (const session of parsed.sessions || []) {
        if (session?.id) this.sessions.set(session.id, session);
      }
    } catch (err) {
      if (err.code !== 'ENOENT') throw err;
    }
  }

  _persist() {
    if (!this.persistencePath) return;
    const dir = path.dirname(this.persistencePath);
    fs.mkdirSync(dir, { recursive: true });
    const tmp = `${this.persistencePath}.tmp`;
    fs.writeFileSync(tmp, JSON.stringify({ sessions: [...this.sessions.values()] }, null, 2));
    fs.renameSync(tmp, this.persistencePath);
  }
}

const defaultSessionStore = new AgentSessionStore();

module.exports = {
  AgentSessionStore,
  createSessionId,
  defaultSessionStore,
};
