import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
import path from "node:path";
import type { Address, Hex } from "viem";

export type SolverJobPhase = "committed" | "revealed" | "finalized" | "expired" | "dry-run" | "skipped-unsat";

export interface SolverJob {
  bountyId: string;
  bountyCode: string;
  phase: SolverJobPhase;
  solver: Address;
  solutionRef: string;
  solutionDigest: Hex;
  solutionKind: number;
  proofFormat: number;
  salt: Hex;
  commitHash: Hex;
  submissionId?: string;
  commitTxHash?: Hex;
  revealTxHash?: Hex;
  finalizedTxHash?: Hex;
  solverWon?: boolean;
  createdAt: string;
  updatedAt: string;
}

export interface SolverStateFile {
  version: 1;
  jobs: Record<string, SolverJob>;
}

function emptyState(): SolverStateFile {
  return { version: 1, jobs: {} };
}

export class SolverState {
  private data: SolverStateFile = emptyState();

  constructor(private readonly filePath: string) {}

  async load() {
    try {
      const parsed = JSON.parse(await readFile(this.filePath, "utf8")) as SolverStateFile;
      this.data = parsed.version === 1 && parsed.jobs ? parsed : emptyState();
    } catch {
      this.data = emptyState();
    }
  }

  allJobs() {
    return Object.values(this.data.jobs).sort((left, right) => left.bountyId.localeCompare(right.bountyId));
  }

  jobForBounty(bountyId: string) {
    return this.data.jobs[bountyId];
  }

  hasLiveJobForBounty(bountyId: string) {
    const job = this.jobForBounty(bountyId);
    return Boolean(job && !["finalized", "expired", "dry-run"].includes(job.phase));
  }

  activeJobCount() {
    return this.allJobs().filter((job) => !["finalized", "expired", "dry-run"].includes(job.phase)).length;
  }

  async upsert(job: SolverJob) {
    const now = new Date().toISOString();
    this.data.jobs[job.bountyId] = {
      ...job,
      updatedAt: now
    };
    await this.save();
  }

  private async save() {
    await mkdir(path.dirname(this.filePath), { recursive: true });
    const tempPath = `${this.filePath}.tmp`;
    await writeFile(tempPath, `${JSON.stringify(this.data, null, 2)}\n`, "utf8");
    await rename(tempPath, this.filePath);
  }
}
