/**
 * PostgreSQL run store implementation
 * Uses Prisma to persist runs in PostgreSQL
 */

import { RunStore } from './runStore.js';
import { Run, CreateRunData, UpdateRunData, ListRunsFilter, RunStatus } from './types.js';
import { getPrismaClient } from '../db/prisma.js';

// Map between our RunStatus and Prisma's RunStatus enum
// Use string literals to avoid build-time errors when client isn't generated
function toPrismaStatus(status: RunStatus): string {
  switch (status) {
    case 'created': return 'CREATED';
    case 'queued': return 'QUEUED';
    case 'running': return 'RUNNING';
    case 'completed': return 'COMPLETED';
    case 'failed': return 'FAILED';
    case 'killed': return 'KILLED';
    default: return 'CREATED';
  }
}

function fromPrismaStatus(status: any): RunStatus {
  const statusStr = String(status).toUpperCase();
  switch (statusStr) {
    case 'CREATED': return 'created';
    case 'QUEUED': return 'queued';
    case 'RUNNING': return 'running';
    case 'COMPLETED': return 'completed';
    case 'SUCCEEDED': return 'completed';
    case 'FAILED': return 'failed';
    case 'KILLED': return 'killed';
    default: return 'created';
  }
}

function toRun(dbRun: any): Run {
  return {
    id: dbRun.id,
    name: dbRun.name,
    status: fromPrismaStatus(dbRun.status),
    input: dbRun.input,
    output: dbRun.output,
    logs: Array.isArray(dbRun.logs) ? dbRun.logs : [],
    clientToken: dbRun.clientToken,
    tags: Array.isArray(dbRun.tags) ? dbRun.tags : [],
    startedAt: dbRun.startedAt?.toISOString() || null,
    completedAt: dbRun.completedAt?.toISOString() || null,
    createdAt: dbRun.createdAt.toISOString(),
    updatedAt: dbRun.updatedAt.toISOString(),
  };
}

export class PostgresRunStore implements RunStore {
  private prisma: any;
  private prismaPromise: Promise<any> | null = null;

  constructor(prisma?: any) {
    // Allow injection for testing, otherwise use lazy initialization
    if (prisma) {
      this.prisma = prisma;
    }
    // Don't initialize prisma here - wait until first use (lazy initialization)
    // This allows the container to start even if Prisma client isn't generated yet
  }

  private async ensurePrisma(): Promise<any> {
    if (this.prisma) {
      return this.prisma;
    }
    if (this.prismaPromise) {
      return await this.prismaPromise;
    }
    // Lazy initialization - only get Prisma client when actually needed
    this.prismaPromise = getPrismaClient().then(client => {
      this.prisma = client;
      return client;
    });
    return await this.prismaPromise;
  }

  async createRun(data: CreateRunData): Promise<Run> {
    const prisma = await this.ensurePrisma();
    const runId = data.id || undefined; // Let Prisma generate UUID if not provided
    
    const dbRun = await prisma.run.create({
      data: {
        id: runId,
        name: data.name || 'Test Run',
        status: toPrismaStatus('created'),
        input: data.input || null,
        logs: [],
        clientToken: data.clientToken || null,
        tags: data.tags || [],
      },
    });

    return toRun(dbRun);
  }

  async getRunById(id: string): Promise<Run | null> {
    const prisma = await this.ensurePrisma();
    const dbRun = await prisma.run.findUnique({
      where: { id },
    });

    return dbRun ? toRun(dbRun) : null;
  }

  async getRunByClientToken(clientToken: string): Promise<Run | null> {
    const prisma = await this.ensurePrisma();
    const dbRun = await prisma.run.findUnique({
      where: { clientToken },
    });

    return dbRun ? toRun(dbRun) : null;
  }

  async listRuns(filter?: ListRunsFilter): Promise<Run[]> {
    const prisma = await this.ensurePrisma();
    const where: any = {};

    if (filter?.tag) {
      where.tags = {
        has: filter.tag,
      };
    }

    if (filter?.status) {
      where.status = toPrismaStatus(filter.status);
    }

    const dbRuns = await prisma.run.findMany({
      where,
      orderBy: { createdAt: 'desc' },
      take: filter?.limit,
    });

    return dbRuns.map(toRun);
  }

  async updateRunStatus(
    id: string,
    status: RunStatus,
    extra?: UpdateRunData
  ): Promise<void> {
    const updateData: any = {
      status: toPrismaStatus(status),
    };

    if (extra) {
      if (extra.input !== undefined) updateData.input = extra.input;
      if (extra.output !== undefined) updateData.output = extra.output;
      if (extra.logs !== undefined) updateData.logs = extra.logs;
      if (extra.startedAt !== undefined) {
        updateData.startedAt = extra.startedAt ? new Date(extra.startedAt) : null;
      }
      if (extra.completedAt !== undefined) {
        updateData.completedAt = extra.completedAt ? new Date(extra.completedAt) : null;
      }
    }

    // Auto-set timestamps based on status
    if (status === 'running' && !updateData.startedAt) {
      updateData.startedAt = new Date();
    }
    if ((status === 'completed' || status === 'failed') && !updateData.completedAt) {
      updateData.completedAt = new Date();
    }

    const prisma = await this.ensurePrisma();
    await prisma.run.update({
      where: { id },
      data: updateData,
    });
  }

  async deleteRun(id: string): Promise<void> {
    const prisma = await this.ensurePrisma();
    await prisma.run.delete({
      where: { id },
    });
  }
}

