import type { ObsidianLiveSyncSettings, SettingsMigrationState } from "@vrtmrz/livesync-commonlib/settings";
import type { CompatibilityPause } from "@/common/databaseCompatibility.ts";
import {
    REVIEW_HARNESS_SCENARIOS,
    REVIEW_HARNESS_STATE_KEY,
    formatReviewHarnessReport,
    inspectCompatibilityReview,
    inspectSettingsLifecycle,
    parsePendingReviewRun,
    type ReviewHarnessReportInput,
    type ReviewHarnessScenarioId,
    type ReviewHarnessScenarioResult,
} from "./reviewHarnessContract";

export interface ReviewHarnessRuntime {
    now(): Date;
    getSettings(): Partial<ObsidianLiveSyncSettings>;
    getNewVaultSettings(): Partial<ObsidianLiveSyncSettings>;
    getSettingsMigrationState(): SettingsMigrationState | undefined;
    isCompatibilityReviewInitialised(): boolean;
    getCompatibilityPause(): CompatibilityPause | undefined;
    openCompatibilityReview(): Promise<void>;
    runVaultRoundTrip(): Promise<ReviewHarnessScenarioResult>;
    readContinuation(): string | null;
    writeContinuation(value: string): void;
    deleteContinuation(): void;
    restart(): void;
    reportError(error: unknown): void;
    copyText(value: string): Promise<void>;
    getEnvironment(): ReviewHarnessReportInput["environment"];
}

export interface ReviewHarnessTranscriptEntry {
    readonly at: string;
    readonly event: string;
    readonly detail?: string;
}

export interface ReviewHarnessSnapshot {
    readonly results: Record<ReviewHarnessScenarioId, ReviewHarnessScenarioResult>;
    readonly running: boolean;
    readonly current: ReviewHarnessScenarioId | null;
    readonly resumedRequestId: string | null;
    readonly continuationError: string | null;
    readonly transcript: readonly ReviewHarnessTranscriptEntry[];
}

const idleResult = (): ReviewHarnessScenarioResult => ({
    status: "idle",
    detail: "Not run",
    observations: [],
});

function initialResults(): Record<ReviewHarnessScenarioId, ReviewHarnessScenarioResult> {
    return Object.fromEntries(REVIEW_HARNESS_SCENARIOS.map(({ id }) => [id, idleResult()])) as Record<
        ReviewHarnessScenarioId,
        ReviewHarnessScenarioResult
    >;
}

export class ReviewHarnessController {
    private readonly results = initialResults();
    private readonly transcript: ReviewHarnessTranscriptEntry[] = [];
    private readonly listeners = new Set<() => void>();
    private running = false;
    private current: ReviewHarnessScenarioId | null = null;
    private resumedRequestId: string | null = null;
    private continuationError: string | null = null;

    constructor(private readonly runtime: ReviewHarnessRuntime) {}

    subscribe(listener: () => void): () => void {
        this.listeners.add(listener);
        return () => this.listeners.delete(listener);
    }

    private notify(): void {
        for (const listener of this.listeners) listener();
    }

    private record(event: string, detail?: string): void {
        this.transcript.push({
            at: this.runtime.now().toISOString(),
            event,
            ...(detail ? { detail } : {}),
        });
        while (this.transcript.length > 100) this.transcript.shift();
    }

    snapshot(): ReviewHarnessSnapshot {
        return {
            results: Object.fromEntries(
                REVIEW_HARNESS_SCENARIOS.map(({ id }) => [id, { ...this.results[id] }])
            ) as Record<ReviewHarnessScenarioId, ReviewHarnessScenarioResult>,
            running: this.running,
            current: this.current,
            resumedRequestId: this.resumedRequestId,
            continuationError: this.continuationError,
            transcript: [...this.transcript],
        };
    }

    consumeContinuation(): void {
        const serialised = this.runtime.readContinuation();
        if (!serialised) return;
        this.runtime.deleteContinuation();
        const parsed = parsePendingReviewRun(serialised);
        if (!parsed.pendingRun) {
            this.continuationError = parsed.error ?? "The Review Harness continuation was invalid.";
            this.results["compatibility-review"] = {
                status: "failed",
                detail: "The stored continuation was invalid and was removed.",
                observations: [],
            };
            this.record("continuation-rejected");
            this.notify();
            return;
        }
        this.resumedRequestId = parsed.pendingRun.requestId;
        this.results["compatibility-review"] = {
            status: "waiting-for-user",
            detail: "Obsidian returned after the requested restart. Open the compatibility review to continue.",
            observations: [`requestedAt=${parsed.pendingRun.requestedAt}`],
        };
        this.record("continuation-consumed", parsed.pendingRun.requestId);
        this.notify();
    }

    async runAutomaticScenarios(): Promise<void> {
        await this.runScenario("settings-lifecycle");
    }

    async runAllScenarios(): Promise<void> {
        for (const { id } of REVIEW_HARNESS_SCENARIOS) await this.runScenario(id);
    }

    private inspectCompatibilityReview(): ReviewHarnessScenarioResult {
        return inspectCompatibilityReview({
            migration: this.runtime.getSettingsMigrationState(),
            reviewInitialised: this.runtime.isCompatibilityReviewInitialised(),
            pendingPause: this.runtime.getCompatibilityPause(),
        });
    }

    async runScenario(id: ReviewHarnessScenarioId): Promise<void> {
        if (this.running) return;
        this.running = true;
        this.current = id;
        this.results[id] = { status: "running", detail: "Running", observations: [] };
        this.record("scenario-started", id);
        this.notify();
        try {
            let result: ReviewHarnessScenarioResult;
            if (id === "settings-lifecycle") {
                result = inspectSettingsLifecycle({
                    migration: this.runtime.getSettingsMigrationState(),
                    settings: this.runtime.getSettings(),
                    newVaultSettings: this.runtime.getNewVaultSettings(),
                });
            } else if (id === "vault-round-trip") {
                result = await this.runtime.runVaultRoundTrip();
            } else {
                const inspection = this.inspectCompatibilityReview();
                result =
                    inspection.status === "failed" || !this.runtime.getCompatibilityPause()
                        ? inspection
                        : {
                              status: "waiting-for-user",
                              detail: "Open the device-local compatibility review and complete its explicit action.",
                              observations: inspection.observations,
                          };
            }
            this.results[id] = result;
            this.record("scenario-updated", `${id}:${result.status}`);
        } catch (error) {
            this.setUnexpectedFailure(id, error);
        } finally {
            this.running = false;
            this.current = null;
            this.notify();
        }
    }

    async openCompatibilityReview(): Promise<void> {
        if (this.running) return;
        this.running = true;
        this.current = "compatibility-review";
        this.notify();
        try {
            const pauseWasPending = this.runtime.getCompatibilityPause() !== undefined;
            await this.runtime.openCompatibilityReview();
            const inspection = this.inspectCompatibilityReview();
            this.results["compatibility-review"] =
                inspection.status === "passed" && !this.runtime.getCompatibilityPause()
                    ? {
                          status: "passed",
                          detail: pauseWasPending
                              ? "The device-local compatibility pause was reviewed and cleared."
                              : inspection.detail,
                          observations: inspection.observations,
                      }
                    : inspection.status === "failed"
                      ? inspection
                      : {
                            status: "waiting-for-user",
                            detail: "The device-local compatibility review remains pending.",
                            observations: inspection.observations,
                        };
            this.record(
                "compatibility-review-updated",
                this.results["compatibility-review"].status
            );
        } catch (error) {
            this.setUnexpectedFailure("compatibility-review", error);
        } finally {
            this.running = false;
            this.current = null;
            this.notify();
        }
    }

    private setUnexpectedFailure(id: ReviewHarnessScenarioId, error: unknown): void {
        this.runtime.reportError(error);
        this.results[id] = {
            status: "failed",
            detail: "The scenario failed unexpectedly. Review the in-app logs for diagnostic details.",
            observations: [],
        };
        this.record("scenario-failed", id);
    }

    prepareCompatibilityReviewRestart(): void {
        const requestedAt = this.runtime.now().toISOString();
        const pending = {
            formatVersion: 1,
            requestId: `compatibility-review-${requestedAt}`,
            scenarioId: "compatibility-review",
            stage: "awaiting-restart",
            requestedAt,
        } as const;
        this.runtime.writeContinuation(JSON.stringify(pending));
        this.results["compatibility-review"] = {
            status: "waiting-for-user",
            detail: "Restart requested. The one-shot continuation will be removed before the review resumes.",
            observations: [],
        };
        this.record("restart-requested", pending.requestId);
        this.notify();
        this.runtime.restart();
    }

    createReport(): string {
        return formatReviewHarnessReport({
            generatedAt: this.runtime.now().toISOString(),
            environment: this.runtime.getEnvironment(),
            scenarios: REVIEW_HARNESS_SCENARIOS.map(({ id, title, mode }) => ({
                id,
                title,
                mode,
                status: this.results[id].status,
                detail: this.results[id].detail,
            })),
            transcript: this.transcript,
        });
    }

    async copyReport(): Promise<void> {
        await this.runtime.copyText(this.createReport());
        this.record("report-copied");
        this.notify();
    }
}

export { REVIEW_HARNESS_STATE_KEY };
