import type { D1DatabaseLike } from "../d1.ts";
import type {
  UploadSession,
  UploadSessionStore,
  UploadedPart,
} from "../uploads.ts";

interface UploadRow {
  id: string;
  r2_upload_id: string;
  object_key: string;
  file_name: string;
  mime_type: string;
  format: "epub" | "pdf";
  total_bytes: number;
  part_size: number;
  part_count: number;
  uploaded_parts_json: string;
  status: UploadSession["status"];
  expires_at: string;
  created_at: string;
  updated_at: string;
}

function fromRow(row: UploadRow): UploadSession {
  let uploadedParts: UploadedPart[] = [];
  try {
    const parsed = JSON.parse(row.uploaded_parts_json) as unknown;
    if (Array.isArray(parsed)) uploadedParts = parsed as UploadedPart[];
  } catch {
    uploadedParts = [];
  }
  return {
    id: row.id,
    r2UploadId: row.r2_upload_id,
    objectKey: row.object_key,
    fileName: row.file_name,
    mimeType: row.mime_type,
    format: row.format,
    totalBytes: row.total_bytes,
    partSize: row.part_size,
    partCount: row.part_count,
    uploadedParts,
    status: row.status,
    expiresAt: row.expires_at,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}

export class D1UploadSessionStore implements UploadSessionStore {
  private readonly database: D1DatabaseLike;

  constructor(database: D1DatabaseLike) {
    this.database = database;
  }

  async create(session: UploadSession): Promise<void> {
    await this.database
      .prepare(`
        INSERT INTO upload_sessions (
          id, r2_upload_id, object_key, file_name, mime_type, format,
          total_bytes, part_size, part_count, uploaded_parts_json, status,
          expires_at, created_at, updated_at
        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      `)
      .bind(
        session.id,
        session.r2UploadId,
        session.objectKey,
        session.fileName,
        session.mimeType,
        session.format,
        session.totalBytes,
        session.partSize,
        session.partCount,
        JSON.stringify(session.uploadedParts),
        session.status,
        session.expiresAt,
        session.createdAt,
        session.updatedAt,
      )
      .run();
  }

  async get(id: string): Promise<UploadSession | null> {
    const row = await this.database
      .prepare("SELECT * FROM upload_sessions WHERE id = ? LIMIT 1")
      .bind(id)
      .first<UploadRow>();
    return row ? fromRow(row) : null;
  }

  async findExpired(expiresBefore: string, limit = 25): Promise<UploadSession[]> {
    const boundedLimit = Math.min(100, Math.max(1, Math.trunc(limit)));
    const result = await this.database
      .prepare(`
        SELECT * FROM upload_sessions
        WHERE status IN ('created', 'uploading', 'completing')
          AND expires_at <= ?
        ORDER BY expires_at ASC
        LIMIT ?
      `)
      .bind(expiresBefore, boundedLimit)
      .all<UploadRow>();
    return (result.results ?? []).map(fromRow);
  }

  async save(session: UploadSession): Promise<void> {
    const result = await this.database
      .prepare(`
        UPDATE upload_sessions
        SET uploaded_parts_json = ?, status = ?, updated_at = ?
        WHERE id = ?
      `)
      .bind(
        JSON.stringify(session.uploadedParts),
        session.status,
        session.updatedAt,
        session.id,
      )
      .run();
    if (result.meta?.changes === 0) {
      throw new Error("找不到该上传任务。");
    }
  }
}
