import type { Session, SessionState } from "@coder-studio/core";
import type { SessionUpdatePatch } from "../../session/types.js";
import { readJsonFile, writeJsonFileAtomic } from "./json-file-store.js";

/**
 * Database row representation for Session table
 */
export interface SessionRow {
  id: string;
  workspace_id: string;
  terminal_id: string;
  provider_id: string;
  capability: "full" | "limited" | "unsupported";
  state: SessionState;
  started_at: number | null;
  ended_at: number | null;
  last_active_at: number;
  completion_percent: number | null;
  error_reason: string | null;
  archived: number; // SQLite uses 0/1 for boolean
  title: string | null;
  first_submitted_user_input?: string | null;
  draft?: string | null;
}

export function rowToSession(row: SessionRow): Session {
  return {
    id: row.id,
    workspaceId: row.workspace_id,
    terminalId: row.terminal_id,
    providerId: row.provider_id,
    state: row.state,
    capability: row.capability,
    startedAt: row.started_at ?? row.last_active_at,
    lastActiveAt: row.last_active_at,
    endedAt: row.ended_at ?? undefined,
    completionPercent: row.completion_percent ?? undefined,
    errorReason: row.error_reason ?? undefined,
    title: row.title ?? undefined,
    firstSubmittedUserInput: row.first_submitted_user_input ?? undefined,
    ...(row.draft != null ? { draft: row.draft } : {}),
  };
}

export function sessionToRow(session: Session & { draft?: string }): SessionRow {
  return {
    id: session.id,
    workspace_id: session.workspaceId,
    terminal_id: session.terminalId,
    provider_id: session.providerId,
    state: session.state,
    capability: session.capability,
    started_at: session.startedAt ?? session.lastActiveAt,
    last_active_at: session.lastActiveAt,
    ended_at: session.endedAt ?? null,
    completion_percent: session.completionPercent ?? null,
    error_reason: session.errorReason ?? null,
    archived: 0,
    draft: session.draft ?? null,
    title: session.title ?? null,
    first_submitted_user_input: session.firstSubmittedUserInput ?? null,
  };
}

/**
 * Input type for creating a new session
 */
export interface NewSession {
  id: string;
  workspaceId: string;
  terminalId: string;
  providerId: string;
  state: SessionState;
  capability: "full" | "limited" | "unsupported";
  startedAt: number;
  lastActiveAt: number;
  completionPercent?: number;
  errorReason?: string;
}

interface StoredSession extends Session {
  archived?: boolean;
}

interface SessionFileRecord {
  version: 1;
  sessions: Record<string, StoredSession>;
}

export interface SessionRepoOptions {
  filePath: string;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

function isSession(value: unknown): value is Session {
  if (!isRecord(value)) {
    return false;
  }

  return (
    typeof value.id === "string" &&
    typeof value.workspaceId === "string" &&
    typeof value.terminalId === "string" &&
    typeof value.providerId === "string" &&
    typeof value.startedAt === "number" &&
    typeof value.lastActiveAt === "number" &&
    (value.capability === "full" ||
      value.capability === "limited" ||
      value.capability === "unsupported") &&
    typeof value.state === "string"
  );
}

function normalizeStoredSession(session: StoredSession): StoredSession {
  return {
    ...session,
    ...(session.title === undefined ? {} : { title: session.title }),
  };
}

function normalizeSessionFile(value: unknown): Record<string, StoredSession> {
  if (isRecord(value) && value.version === 1 && isRecord(value.sessions)) {
    const normalized: Record<string, StoredSession> = {};
    for (const entry of Object.values(value.sessions)) {
      if (isSession(entry)) {
        normalized[entry.id] = normalizeStoredSession(entry as StoredSession);
      }
    }
    return normalized;
  }

  if (Array.isArray(value)) {
    const normalized: Record<string, StoredSession> = {};
    for (const entry of value) {
      if (isSession(entry)) {
        normalized[entry.id] = normalizeStoredSession(entry as StoredSession);
      }
    }
    return normalized;
  }

  if (isRecord(value)) {
    const normalized: Record<string, StoredSession> = {};
    for (const [sessionId, entry] of Object.entries(value)) {
      if (isSession(entry)) {
        normalized[sessionId] = normalizeStoredSession({
          ...(entry as StoredSession),
          id: sessionId,
        });
      }
    }
    return normalized;
  }

  return {};
}

/**
 * Session repository for CRUD operations
 */
export class SessionRepo {
  private readonly filePath: string;

  constructor(input: SessionRepoOptions) {
    this.filePath = input.filePath;
  }

  private loadFileSessions(): Record<string, StoredSession> {
    const parsed = readJsonFile<
      SessionFileRecord | Record<string, StoredSession> | StoredSession[]
    >(this.filePath);
    if (parsed !== undefined) {
      return normalizeSessionFile(parsed);
    }

    return {};
  }

  private saveFileSessions(sessions: Record<string, StoredSession>): void {
    const payload: SessionFileRecord = {
      version: 1,
      sessions,
    };
    writeJsonFileAtomic(this.filePath, payload);
  }

  private sortSessions(sessions: Record<string, StoredSession>): Session[] {
    return Object.values(sessions)
      .sort((a, b) => b.startedAt - a.startedAt)
      .map(({ archived: _archived, ...session }) => session);
  }

  private listAllStoredSessions(): StoredSession[] {
    return Object.values(this.loadFileSessions()).sort((a, b) => b.startedAt - a.startedAt);
  }

  /**
   * Lists all sessions for a workspace
   */
  listByWorkspace(workspaceId: string): Session[] {
    return this.sortSessions(this.loadFileSessions()).filter(
      (session) => session.workspaceId === workspaceId
    );
  }

  /**
   * Finds a session by ID
   */
  findById(id: string): Session | undefined {
    const stored = this.loadFileSessions()[id];
    if (!stored) {
      return undefined;
    }

    const { archived: _archived, ...session } = stored;
    return session;
  }

  /**
   * Finds a session by terminal ID (1:1 relationship)
   */
  findByTerminalId(terminalId: string): Session | undefined {
    const stored = this.listAllStoredSessions().find(
      (session) => session.terminalId === terminalId
    );
    if (!stored) {
      return undefined;
    }

    const { archived: _archived, ...session } = stored;
    return session;
  }

  /**
   * Lists all active (non-ended) sessions for a workspace
   */
  listActiveByWorkspace(workspaceId: string): Session[] {
    return this.listByWorkspace(workspaceId).filter((session) => session.endedAt == null);
  }

  /**
   * Creates a new session
   */
  create(session: NewSession): Session {
    const next: StoredSession = {
      id: session.id,
      workspaceId: session.workspaceId,
      terminalId: session.terminalId,
      providerId: session.providerId,
      state: session.state,
      capability: session.capability,
      startedAt: session.startedAt,
      lastActiveAt: session.lastActiveAt,
      ...(session.completionPercent !== undefined
        ? { completionPercent: session.completionPercent }
        : {}),
      ...(session.errorReason !== undefined ? { errorReason: session.errorReason } : {}),
    };

    const sessions = this.loadFileSessions();
    sessions[next.id] = next;
    this.saveFileSessions(sessions);

    const { archived: _archived, ...created } = next;
    return created;
  }

  insert(session: SessionRow): void {
    const next: StoredSession = {
      ...rowToSession(session),
      ...(session.archived === 1 ? { archived: true } : {}),
    };

    const sessions = this.loadFileSessions();
    sessions[next.id] = next;
    this.saveFileSessions(sessions);
  }

  update(id: string, patch: SessionUpdatePatch): void {
    const sessions = this.loadFileSessions();
    const current = sessions[id];
    if (!current) {
      return;
    }

    const next: StoredSession = {
      ...current,
      ...(patch.terminalId !== undefined ? { terminalId: patch.terminalId } : {}),
      ...(patch.state !== undefined ? { state: patch.state as SessionState } : {}),
      ...(patch.startedAt !== undefined ? { startedAt: patch.startedAt } : {}),
      ...(patch.endedAt !== undefined
        ? patch.endedAt === null
          ? { endedAt: undefined }
          : { endedAt: patch.endedAt }
        : {}),
      ...(patch.completionPercent !== undefined
        ? patch.completionPercent === null
          ? { completionPercent: undefined }
          : { completionPercent: patch.completionPercent }
        : {}),
      ...(patch.errorReason !== undefined
        ? patch.errorReason === null
          ? { errorReason: undefined }
          : { errorReason: patch.errorReason }
        : {}),
      ...(patch.lastActiveAt !== undefined ? { lastActiveAt: patch.lastActiveAt } : {}),
      ...(patch.title !== undefined
        ? patch.title === null
          ? { title: undefined }
          : { title: patch.title }
        : {}),
      ...(patch.firstSubmittedUserInput !== undefined
        ? patch.firstSubmittedUserInput === null
          ? { firstSubmittedUserInput: undefined }
          : { firstSubmittedUserInput: patch.firstSubmittedUserInput }
        : {}),
    };

    sessions[id] = next;
    this.saveFileSessions(sessions);
  }

  findByWorkspaceId(workspaceId: string): Session[] {
    return this.listByWorkspace(workspaceId);
  }

  listHydratable(): Session[] {
    return this.listAllStoredSessions()
      .filter((session) => !session.archived && session.endedAt == null)
      .map(({ archived: _archived, ...session }) => session);
  }

  /**
   * Updates session state
   */
  updateState(id: string, state: SessionState): void {
    this.update(id, { state });
  }

  /**
   * Updates last active timestamp
   */
  updateLastActive(id: string, lastActiveAt: number): void {
    this.update(id, { lastActiveAt });
  }

  /**
   * Marks a session as ended
   */
  markEnded(id: string, endedAt: number): void {
    this.update(id, { endedAt, state: "ended" });
  }

  /**
   * Updates completion percent (for full capability sessions)
   */
  updateCompletionPercent(id: string, completionPercent: number): void {
    this.update(id, { completionPercent });
  }

  /**
   * Sets error reason
   */
  setError(id: string, errorReason: string): void {
    this.update(id, { errorReason });
  }

  /**
   * Archives a session
   */
  archive(id: string): void {
    const sessions = this.loadFileSessions();
    const current = sessions[id];
    if (!current) {
      return;
    }

    sessions[id] = {
      ...current,
      archived: true,
    };
    this.saveFileSessions(sessions);
  }

  /**
   * Deletes a session by ID
   */
  delete(id: string): void {
    const sessions = this.loadFileSessions();
    if (!sessions[id]) {
      return;
    }

    delete sessions[id];
    this.saveFileSessions(sessions);
  }
}
