import fs from "node:fs/promises";
import path from "node:path";
import type { StoredArtifactRecord } from "../../src/types/storage.js";
import { isReviewOnlyArtifactSource, resolveStoredArtifactSource } from "../../src/lib/artifactStore.js";
import type { ArtifactStoreLoadResult, ArtifactStoreRepositoryPort, ArtifactStoreSaveResult } from "./contracts.js";
interface ArtifactStoreFile {
  version?: number;
  artifacts?: StoredArtifactRecord[];
}

export class JsonArtifactStoreRepository implements ArtifactStoreRepositoryPort {
  private readonly filePath: string;

  constructor(userDataPath: string, fileName = "artifact-store.json") {
    this.filePath = path.join(userDataPath, fileName);
  }

  async loadAll(): Promise<ArtifactStoreLoadResult> {
    const records = await this.loadMap();
    const artifacts = [...records.values()].sort((a, b) => (b.lastSeenAt ?? "").localeCompare(a.lastSeenAt ?? ""));
    return { ok: true, artifacts, total: artifacts.length, path: this.filePath };
  }

  async loadMap(): Promise<Map<string, StoredArtifactRecord>> {
    try {
      const raw = JSON.parse(await fs.readFile(this.filePath, "utf8")) as ArtifactStoreFile;
      const records = Array.isArray(raw.artifacts) ? raw.artifacts : [];
      const cleaned = records
        .filter((record) => record?.id && !isObviousGarbageRecord(record))
        .map((record) => normalizeStoredArtifactRecordForLoad(record));
      const store = new Map(cleaned.map((record) => [record.id, record]));
      const storeChanged = cleaned.length !== records.length || cleaned.some((record, index) => JSON.stringify(record) !== JSON.stringify(records[index]));
      if (storeChanged) await this.writeRecords([...store.values()]);
      return store;
    } catch {
      return new Map();
    }
  }

  async saveMany(records: StoredArtifactRecord[]): Promise<ArtifactStoreSaveResult> {
    const store = await this.loadMap();
    const now = new Date().toISOString();
    let added = 0;
    let updated = 0;

    for (const record of records ?? []) {
      if (!record?.id || isObviousGarbageRecord(record)) continue;
      const existing = store.get(record.id);
      if (existing) {
        store.set(record.id, {
          ...existing,
          ...record,
          firstSeenAt: existing.firstSeenAt ?? now,
          lastSeenAt: now,
          timesSeen: (existing.timesSeen ?? 1) + 1,
          confidence: Math.max(existing.confidence ?? 0, record.confidence ?? 0),
          // A later confident scan clears the review flag; an uncertain rescan
          // must not downgrade an already confirmed artifact.
          needsReview: Boolean(existing.needsReview) && Boolean(record.needsReview),
          source: resolveStoredArtifactSource(existing.source, record.source),
        });
        updated++;
      } else {
        const mergeCandidate = [...store.values()].find((candidate) => shouldMergeArtifactRecords(candidate, record));
        if (mergeCandidate) {
          const merged = mergeArtifactRecords(mergeCandidate, record, now);
          if (mergeCandidate.id !== merged.id) store.delete(mergeCandidate.id);
          store.set(merged.id, merged);
          updated++;
        } else {
          store.set(record.id, { ...record, firstSeenAt: now, lastSeenAt: now, timesSeen: 1 });
          added++;
        }
      }
    }

    await this.writeRecords([...store.values()]);
    return { ok: true, added, updated, total: store.size, path: this.filePath };
  }

  private async writeRecords(records: StoredArtifactRecord[]) {
    await fs.mkdir(path.dirname(this.filePath), { recursive: true });
    await fs.writeFile(this.filePath, JSON.stringify({ version: 1, artifacts: records }, null, 2), "utf8");
  }
}

function artifactMergeKey(record: StoredArtifactRecord) {
  return [record.name, record.slot, record.setName, record.mainStat, record.mainValue, record.level ?? ""]
    .map((value) => `${value ?? ""}`.trim().toLowerCase())
    .join("::");
}

function artifactFamilyKey(record: StoredArtifactRecord) {
  return [record.name, record.slot, record.setName, record.mainStat]
    .map((value) => `${value ?? ""}`.trim().toLowerCase())
    .join("::");
}

function artifactQualityScore(record: StoredArtifactRecord) {
  const corePenalty =
    (record.name === "Unknown artifact" ? 40 : 0)
    + (record.slot === "Unknown slot" ? 35 : 0)
    + (record.setName === "Unknown set" ? 35 : 0)
    + (record.mainStat === "Unknown main stat" ? 40 : 0)
    + (record.mainValue === "?" ? 20 : 0);

  return (record.confidence ?? 0)
    + Math.min(20, (record.substats?.length ?? 0) * 5)
    + (record.needsReview ? -12 : 8)
    + (record.equipped && record.equipped !== "Not detected" ? 2 : 0)
    - corePenalty;
}

function isObviousGarbageRecord(record: StoredArtifactRecord) {
  return (
    !record?.id
    || record.name === "Unknown artifact"
    || record.slot === "Unknown slot"
    || record.setName === "Unknown set"
    || record.mainStat === "Unknown main stat"
    || record.mainValue === "?"
    || (record.substats?.length ?? 0) === 0
    || (record.confidence ?? 0) < 30
  );
}

function parseArtifactNumericValue(value: string) {
  const numeric = Number.parseFloat(String(value ?? "").replace(/,/g, "").replace("%", "").trim());
  return Number.isFinite(numeric) ? numeric : 0;
}

function normalizeStoredArtifactRecordForLoad(record: StoredArtifactRecord) {
  const normalizedTimesSeen = Math.max(1, Math.round(record.timesSeen ?? 1));
  const reviewOnly = isReviewOnlyArtifactSource(record.source);
  return {
    ...record,
    timesSeen: reviewOnly ? 1 : normalizedTimesSeen,
    firstSeenAt: record.firstSeenAt ?? record.lastSeenAt,
  };
}

function shouldMergeArtifactRecords(existing: StoredArtifactRecord, incoming: StoredArtifactRecord) {
  if (existing.id === incoming.id) return true;

  const overlap = (incoming.substats ?? []).filter((substat: string) => (existing.substats ?? []).includes(substat)).length;
  const overlapThreshold = Math.min(2, Math.min(existing.substats?.length ?? 0, incoming.substats?.length ?? 0));
  const qualityGap = Math.abs(artifactQualityScore(existing) - artifactQualityScore(incoming)) >= 8;

  if (artifactMergeKey(existing) === artifactMergeKey(incoming)) {
    return (
      overlap >= overlapThreshold
      || existing.needsReview
      || incoming.needsReview
      || (existing.substats?.length ?? 0) !== (incoming.substats?.length ?? 0)
      || qualityGap
    );
  }

  if (artifactFamilyKey(existing) !== artifactFamilyKey(incoming)) return false;

  const sameOrBetterLevel = (incoming.level ?? 0) >= (existing.level ?? 0);
  const sameOrBetterMainValue = parseArtifactNumericValue(incoming.mainValue) >= parseArtifactNumericValue(existing.mainValue);
  return sameOrBetterLevel && sameOrBetterMainValue && (
    overlap >= overlapThreshold
    || existing.needsReview
    || incoming.needsReview
    || (existing.substats?.length ?? 0) !== (incoming.substats?.length ?? 0)
    || qualityGap
  );
}

function mergeArtifactRecords(existing: StoredArtifactRecord, incoming: StoredArtifactRecord, now: string): StoredArtifactRecord {
  const incomingPreferred = artifactQualityScore(incoming) >= artifactQualityScore(existing);
  const preferred = incomingPreferred ? incoming : existing;
  const secondary = incomingPreferred ? existing : incoming;
  const preferredSubstats = (preferred.substats?.length ?? 0) >= (secondary.substats?.length ?? 0) ? preferred.substats : secondary.substats;

  return {
    ...secondary,
    ...preferred,
    id: incomingPreferred ? incoming.id : existing.id,
    level: Math.max(existing.level ?? 0, incoming.level ?? 0),
    substats: [...(preferredSubstats ?? [])],
    equipped: preferred.equipped && preferred.equipped !== "Not detected" ? preferred.equipped : secondary.equipped,
    confidence: Math.max(existing.confidence ?? 0, incoming.confidence ?? 0),
    needsReview: Boolean(existing.needsReview) && Boolean(incoming.needsReview),
    source: resolveStoredArtifactSource(existing.source, incoming.source),
    firstSeenAt: existing.firstSeenAt ?? now,
    lastSeenAt: now,
    timesSeen: (existing.timesSeen ?? 1) + 1,
  };
}
