import type { Dispatch, SetStateAction } from "react";
import {
  applyScannerLearningRules,
  countScannerLearningRules,
  deriveScannerLearningRules,
  deriveScannerLearningRulesFromReviewSamples,
  shouldFlagArtifactForReview,
  type ScannerLearningRules,
} from "../../../lib/scannerLearning";
import { captureRejectionReason, captureSourceRejectionReason, shouldPersistParsedArtifact } from "../../../lib/scannerCaptureQuality";
import { parseArtifactCandidate, type ParsedArtifactCandidate } from "../../../lib/artifactOcrParser";
import { isReviewOnlyArtifactSource, storedArtifactStrength, toStoredArtifact } from "../../../lib/artifactStore";
import {
  type ArtifactRepositoryPort,
  type LearningRepositoryPort,
  type ReviewSampleRepositoryPort,
} from "../../../infrastructure/repositories/rendererBridgeRepositories";
import type { CaptureResult, ReviewSampleRecord, SaveResultWithPath } from "../../../types/global";
import type { StoredArtifactRecord } from "../../../types/storage";

const REVIEW_SAMPLE_LIMIT_INITIAL = 120;
const REVIEW_SAMPLE_LIMIT_QUEUE = 80;

export interface ReviewStateContext {
  artifactRepo?: ArtifactRepositoryPort;
  reviewSamplesRepo?: ReviewSampleRepositoryPort;
  learningRepo?: LearningRepositoryPort;
  onStoredArtifactsChanged?: () => Promise<void> | void;
  setReviewSampleTotal: Dispatch<SetStateAction<number>>;
  setReviewSamples: Dispatch<SetStateAction<ReviewSampleRecord[]>>;
  setLearningRulesLoaded: Dispatch<SetStateAction<boolean>>;
  setScannerLearningRules: Dispatch<SetStateAction<ScannerLearningRules>>;
  setReviewStatus: Dispatch<SetStateAction<string>>;
  setStoredTotal: Dispatch<SetStateAction<number | null>>;
  appendAutomationLog: (line: string) => void;
}

async function loadReviewSamplesForContext(
  context: ReviewStateContext,
  limit: number,
): Promise<ReturnType<ReviewSampleRepositoryPort["loadSamples"]> | null> {
  return context.reviewSamplesRepo?.loadSamples(limit).catch(() => null) ?? null;
}

async function loadReviewSamplesAndSetTotal(context: ReviewStateContext, limit: number) {
  const result = await loadReviewSamplesForContext(context, limit);
  if (result?.ok) {
    context.setReviewSampleTotal(result.total);
  }
  return result;
}

export function parseLearnedArtifact(
  capture: CaptureResult | null,
  scannerLearningRules: ScannerLearningRules,
): ParsedArtifactCandidate | null {
  return parseArtifactCandidate(applyScannerLearningRules(capture, scannerLearningRules));
}

export function createCaptureFromReviewSample(entry: ReviewSampleRecord): CaptureResult | null {
  const capture = entry.sample?.capture;
  if (!capture?.ocr) return null;
  return {
    id: `review-${entry.savedAt}`,
    name: capture.name ?? "review-sample",
    width: capture.width ?? 0,
    height: capture.height ?? 0,
    dataUrl: "",
    capturedAt: capture.capturedAt ?? entry.savedAt,
    captureTarget: capture.captureTarget,
    crops: [],
    inventoryDataUrl: capture.inventoryDataUrl,
    ocr: capture.ocr,
    inventoryGrid: capture.inventoryGrid,
  };
}

function storedArtifactFingerprint(record: StoredArtifactRecord | null | undefined) {
  if (!record) return "";
  return [
    record.name,
    record.slot,
    record.level ?? 0,
    record.mainStat,
    record.mainValue,
    record.setName,
    record.equipped,
    (record.substats ?? []).join("|"),
    record.needsReview ? "review" : "clean",
  ].join("::");
}

function shouldRecoverIntoStore(existing: StoredArtifactRecord | undefined, incoming: StoredArtifactRecord) {
  if (!existing) return true;
  if (storedArtifactFingerprint(existing) === storedArtifactFingerprint(incoming)) return false;

  const existingStrength = storedArtifactStrength(existing);
  const incomingStrength = storedArtifactStrength(incoming);
  const existingSubstats = existing.substats?.length ?? 0;
  const incomingSubstats = incoming.substats?.length ?? 0;
  const existingEquippedKnown = Boolean(existing.equipped && !/not detected/i.test(existing.equipped));
  const incomingEquippedKnown = Boolean(incoming.equipped && !/not detected/i.test(incoming.equipped));

  if (existing.needsReview && !incoming.needsReview) return true;
  if (incomingSubstats > existingSubstats) return true;
  if (!existingEquippedKnown && incomingEquippedKnown) return true;
  if (incomingStrength >= existingStrength + 6) return true;
  if (isReviewOnlyArtifactSource(existing.source)) return true;
  return false;
}

function getDefaultScannerRules(loadedRules: { rules?: ScannerLearningRules } | null | undefined): ScannerLearningRules {
  return { textReplacements: { ...(loadedRules?.rules?.textReplacements ?? {}) } };
}

async function loadReviewSamplesAndRecover(context: ReviewStateContext, rules: ScannerLearningRules, limit = REVIEW_SAMPLE_LIMIT_INITIAL) {
  const reviewResult = await loadReviewSamplesAndSetTotal(context, limit);
  if (reviewResult?.ok) {
    await recoverArtifactsFromReviewSamples(reviewResult.samples, rules, context);
  }
  return reviewResult;
}

export async function recoverArtifactsFromReviewSamples(
  samples: ReviewSampleRecord[],
  rules: ScannerLearningRules,
  context: ReviewStateContext,
): Promise<number> {
  const { artifactRepo, onStoredArtifactsChanged, setStoredTotal, appendAutomationLog } = context;
  if (!artifactRepo?.saveMany || samples.length === 0) return 0;

  const existingResult = await artifactRepo.loadAll().catch(() => null);
  const existingById = new Map(
    (existingResult?.artifacts ?? []).map((record: StoredArtifactRecord) => [record.id, record]),
  );

  const recoveredCandidates = samples.flatMap((entry) => {
    const capture = createCaptureFromReviewSample(entry);
    const parsed = parseLearnedArtifact(capture, rules);
    if (!capture || !parsed) return [];
    if (captureSourceRejectionReason(capture)) return [];
    const needsReview = shouldFlagArtifactForReview(parsed);
    if (!shouldPersistParsedArtifact(parsed, needsReview)) return [];
    const stored = toStoredArtifact(parsed, "review-reprocess", needsReview);
    return shouldRecoverIntoStore(existingById.get(stored.id), stored) ? [stored] : [];
  });

  const recoveredById = new Map<string, StoredArtifactRecord>();
  for (const record of recoveredCandidates) {
    const existing = recoveredById.get(record.id);
    if (!existing || storedArtifactStrength(record) > storedArtifactStrength(existing)) {
      recoveredById.set(record.id, record);
    }
  }
  const recovered = [...recoveredById.values()];

  if (recovered.length === 0) return 0;
  const result = await artifactRepo.saveMany(recovered).catch(() => null);
  if (result?.ok) {
    setStoredTotal(result.total);
    void onStoredArtifactsChanged?.();
    appendAutomationLog(`review reprocess: ${result.added} neu, ${result.updated} aktualisiert`);
    return result.added + result.updated;
  }
  return 0;
}

export async function initializeLearningState(context: ReviewStateContext): Promise<void> {
  const {
    learningRepo,
    setLearningRulesLoaded,
    setScannerLearningRules,
  } = context;

  try {
    const loadedRules = await learningRepo?.loadRules().catch(() => null);
    const currentRules = getDefaultScannerRules(loadedRules);

    if (countScannerLearningRules(currentRules) > 0) {
      setScannerLearningRules(currentRules);
      await loadReviewSamplesAndRecover(context, currentRules);
      setLearningRulesLoaded(true);
      return;
    }

    const reviewResult = await loadReviewSamplesAndRecover(context, { ...currentRules }, REVIEW_SAMPLE_LIMIT_INITIAL);
    const derived = deriveScannerLearningRulesFromReviewSamples(reviewResult?.samples ?? []);
    const activeRules = countScannerLearningRules(derived) > 0 ? derived : currentRules;
    if (countScannerLearningRules(derived) > 0) {
      setScannerLearningRules(derived);
      await learningRepo?.saveRules?.(derived).catch(() => null);
    } else {
      setScannerLearningRules(currentRules);
    }
    setLearningRulesLoaded(true);
  } catch {
    const reviewResult = await loadReviewSamplesAndSetTotal(context, REVIEW_SAMPLE_LIMIT_INITIAL);
    const derived = deriveScannerLearningRulesFromReviewSamples(reviewResult?.samples ?? []);
    setScannerLearningRules(derived);
    if (countScannerLearningRules(derived) > 0) {
      await learningRepo?.saveRules?.(derived).catch(() => null);
    }
    if (reviewResult?.ok) {
      await recoverArtifactsFromReviewSamples(reviewResult.samples, derived, context);
    }
    setLearningRulesLoaded(true);
  }
}

export async function mergeLearningRules(
  nextRules: Partial<ScannerLearningRules> | null | undefined,
  currentRules: ScannerLearningRules,
  context: ReviewStateContext,
) {
  if (!nextRules || countScannerLearningRules(nextRules) === 0) return null;
  const merged = {
    textReplacements: {
      ...currentRules.textReplacements,
      ...(nextRules.textReplacements ?? {}),
    },
  };
  context.setScannerLearningRules(merged);
  const result = await context.learningRepo?.saveRules?.(merged).catch(() => null);
  return { result, merged };
}

export async function persistParsedArtifact(
  capture: CaptureResult | null,
  parsed: ParsedArtifactCandidate,
  source: string,
  needsReview: boolean,
  context: ReviewStateContext,
) {
  const { artifactRepo, onStoredArtifactsChanged, setStoredTotal, appendAutomationLog } = context;
  if (!artifactRepo?.saveMany) return false;

  const rejection = captureRejectionReason(capture, parsed);
  if (rejection) {
    appendAutomationLog(`persist skip: ${rejection}`);
    return false;
  }
  if (!shouldPersistParsedArtifact(parsed, needsReview)) {
    appendAutomationLog(`persist skip: parsed artifact bleibt vorerst nur Review (${parsed.name})`);
    return false;
  }
  try {
    const result = await artifactRepo.saveMany([toStoredArtifact(parsed, source, needsReview)]);
    if (result?.ok) {
      setStoredTotal(result.total);
      void onStoredArtifactsChanged?.();
      return true;
    }
    return false;
  } catch {
    return false;
  }
}

export async function saveReviewSample(
  capture: CaptureResult | null,
  parsed: ParsedArtifactCandidate | null,
  reason: string,
  scannerLearningRules: ScannerLearningRules,
  context: ReviewStateContext,
): Promise<{ result: SaveResultWithPath | null; recoveredParsed: ParsedArtifactCandidate | null; recoveredToDb: boolean }> {
  const {
    reviewSamplesRepo,
    setReviewStatus,
    appendAutomationLog,
    setReviewSamples,
    onStoredArtifactsChanged,
  } = context;

  if (!reviewSamplesRepo?.saveSample) {
    setReviewStatus("Review-Sample speichern ist nur in der Electron-App verfuegbar.");
    return { result: null, recoveredParsed: null, recoveredToDb: false };
  }
  if (!capture) return { result: null, recoveredParsed: null, recoveredToDb: false };

  const result = await reviewSamplesRepo.saveSample({
    reason,
    capture: {
      id: capture.id,
      name: capture.name,
      width: capture.width,
      height: capture.height,
      dataUrl: capture.dataUrl,
      detailDataUrl: capture.detailDataUrl,
      inventoryDataUrl: capture.inventoryDataUrl,
      captureTarget: capture.captureTarget,
      capturedAt: capture.capturedAt,
      crops: capture.crops?.map((crop: NonNullable<CaptureResult["crops"]>[number]) => ({
        id: crop.id,
        label: crop.label,
        rect: crop.rect,
        dataUrl: crop.dataUrl,
      })),
      inventoryGrid: capture.inventoryGrid,
      inventoryCount: capture.inventoryCount,
      ocr: capture.ocr,
    },
    parsed,
  });

  const learned = deriveScannerLearningRules(capture, parsed);
  const learnedResult = await mergeLearningRules(learned, scannerLearningRules, context);
  const learnedCount = countScannerLearningRules(learned);
  const activeRules = learnedResult?.merged ?? scannerLearningRules;
  const recoveredParsed = parseLearnedArtifact(capture, activeRules);
  const recoveredNeedsReview = shouldFlagArtifactForReview(recoveredParsed);
  let recoveredToDb = false;
  if (recoveredParsed && shouldPersistParsedArtifact(recoveredParsed, recoveredNeedsReview)) {
    const extendedContext: ReviewStateContext = {
      ...context,
      artifactRepo: context.artifactRepo,
      onStoredArtifactsChanged,
    };
    recoveredToDb = await persistParsedArtifact(capture, recoveredParsed, "review-recovered", recoveredNeedsReview, extendedContext);
  }

  setReviewStatus(
    result?.path
      ? `Review-Sample gespeichert: ${result.path}${learnedCount > 0 ? ` - ${learnedCount} lokale Lernregeln aktualisiert` : ""}${recoveredToDb ? " - Artifact direkt in DB nachgezogen" : ""}`
      : "Review-Sample konnte nicht gespeichert werden.",
  );
  if (learnedResult?.result?.ok) appendAutomationLog(`learning: ${learnedResult.result.total} aktive Textregeln`);
  if (recoveredToDb && recoveredParsed) appendAutomationLog(`review recovered: ${recoveredParsed.name} +${recoveredParsed.level}`);

  const reviewResult = await loadReviewSamplesAndSetTotal(context, REVIEW_SAMPLE_LIMIT_QUEUE);
  if (reviewResult?.ok) {
    setReviewSamples(reviewResult.samples);
  }

  return { result, recoveredParsed, recoveredToDb };
}

export async function loadReviewQueue(context: ReviewStateContext): Promise<void> {
  const { setReviewSamples } = context;
  const result = await loadReviewSamplesAndSetTotal(context, REVIEW_SAMPLE_LIMIT_QUEUE);
  if (!result?.ok) return;
  setReviewSamples(result.samples);
}
