import type { ObsidianLiveSyncSettings, SettingsMigrationState } from "@vrtmrz/livesync-commonlib/settings";
import type { CompatibilityPause } from "@/common/databaseCompatibility.ts";
import type {
    ReviewHarnessScenarioResult,
    ReviewHarnessScenarioStatus,
} from "./reviewHarnessTypes";

export type { ReviewHarnessScenarioResult, ReviewHarnessScenarioStatus } from "./reviewHarnessTypes";

export const REVIEW_HARNESS_SCENARIOS = [
    {
        id: "settings-lifecycle",
        title: "Settings lifecycle",
        description:
            "Checks whether loaded settings expose the seven synchronisation choices and apply new-Vault recommendations only to a genuinely new Vault.",
        mode: "automatic",
        access: "read-only",
    },
    {
        id: "compatibility-review",
        title: "Compatibility review boundary",
        description:
            "Checks the current device-local compatibility pause, then guides the reviewer through its explicit review and restart boundary.",
        mode: "guided",
        access: "device-local-state",
    },
    {
        id: "vault-round-trip",
        title: "Vault fixture round trip",
        description:
            "Creates, reads, modifies, renames, and removes a fixed owned fixture tree after explicit confirmation.",
        mode: "automatic",
        access: "dedicated-vault-fixtures",
    },
] as const;

export const REVIEW_HARNESS_SCENARIO_IDS = REVIEW_HARNESS_SCENARIOS.map(({ id }) => id);

export type ReviewHarnessScenario = (typeof REVIEW_HARNESS_SCENARIOS)[number];
export type ReviewHarnessScenarioId = ReviewHarnessScenario["id"];
export type ReviewHarnessScenarioMode = ReviewHarnessScenario["mode"];
export type ReviewHarnessScenarioAccess = ReviewHarnessScenario["access"];

export const REVIEW_HARNESS_STATE_KEY = "review-harness-v1";

export interface PendingReviewRun {
    readonly formatVersion: 1;
    readonly requestId: string;
    readonly scenarioId: "compatibility-review";
    readonly stage: "awaiting-restart";
    readonly requestedAt: string;
}

export type ParsedPendingReviewRun = { pendingRun?: PendingReviewRun; error?: string };

function isRecord(value: unknown): value is Record<string, unknown> {
    return typeof value === "object" && value !== null;
}

export function parsePendingReviewRun(serialised: string | null | undefined): ParsedPendingReviewRun {
    if (!serialised) return {};
    let value: unknown;
    try {
        value = JSON.parse(serialised) as unknown;
    } catch {
        return { error: "Review Harness continuation is not valid JSON" };
    }
    if (!isRecord(value)) return { error: "Review Harness continuation must be an object" };
    if (value.formatVersion !== 1) {
        return { error: `Unsupported Review Harness continuation version: ${String(value.formatVersion)}` };
    }
    if (value.scenarioId !== "compatibility-review") {
        return { error: `Unknown Review Harness scenario: ${String(value.scenarioId)}` };
    }
    if (value.stage !== "awaiting-restart") {
        return { error: `Unknown Review Harness continuation stage: ${String(value.stage)}` };
    }
    if (typeof value.requestedAt !== "string") {
        return { error: "Review Harness request time must be an ISO date" };
    }
    const requestedAtMs = Date.parse(value.requestedAt);
    if (Number.isNaN(requestedAtMs) || new Date(requestedAtMs).toISOString() !== value.requestedAt) {
        return { error: "Review Harness request time must be an ISO date" };
    }
    if (value.requestId !== `compatibility-review-${value.requestedAt}`) {
        return { error: "Review Harness request ID does not match the fixed continuation format" };
    }
    return {
        pendingRun: {
            formatVersion: 1,
            requestId: value.requestId,
            scenarioId: value.scenarioId,
            stage: value.stage,
            requestedAt: value.requestedAt,
        },
    };
}

const PRESERVED_SYNC_SETTING_KEYS = [
    "liveSync",
    "syncOnSave",
    "syncOnEditorSave",
    "syncOnStart",
    "syncOnFileOpen",
    "syncAfterMerge",
    "periodicReplication",
] as const;

const NEW_VAULT_RECOMMENDATION_KEYS = [
    "syncMaxSizeInMB",
    "chunkSplitterVersion",
    "usePluginSyncV2",
    "handleFilenameCaseSensitive",
    "E2EEAlgorithm",
] as const;

type LifecycleSettingKey = (typeof PRESERVED_SYNC_SETTING_KEYS)[number] | (typeof NEW_VAULT_RECOMMENDATION_KEYS)[number];
type SettingsForLifecycleInspection = Partial<Pick<ObsidianLiveSyncSettings, LifecycleSettingKey>>;

export function inspectSettingsLifecycle(input: {
    readonly migration: SettingsMigrationState | undefined;
    readonly settings: SettingsForLifecycleInspection;
    readonly newVaultSettings: SettingsForLifecycleInspection;
}): ReviewHarnessScenarioResult {
    if (!input.migration) {
        return {
            status: "failed",
            detail: "The settings service did not expose migration evidence.",
            observations: [],
        };
    }

    const invalidSyncSettings = PRESERVED_SYNC_SETTING_KEYS.filter(
        (key) => typeof input.settings[key] !== "boolean"
    );
    if (invalidSyncSettings.length > 0) {
        return {
            status: "failed",
            detail: `Synchronisation choices are missing or invalid: ${invalidSyncSettings.join(", ")}`,
            observations: [],
        };
    }

    const observations = PRESERVED_SYNC_SETTING_KEYS.map((key) => `${key}=${String(input.settings[key])}`);
    if (!input.migration.isNewVault) {
        return {
            status: "passed",
            detail: `Loaded an existing Vault from settings schema ${input.migration.sourceVersion} to ${input.migration.targetVersion}.`,
            observations,
        };
    }

    const mismatchedRecommendations = NEW_VAULT_RECOMMENDATION_KEYS.filter(
        (key) => input.settings[key] !== input.newVaultSettings[key]
    );
    if (mismatchedRecommendations.length > 0) {
        return {
            status: "failed",
            detail: `The new Vault recommendations differ for: ${mismatchedRecommendations.join(", ")}`,
            observations,
        };
    }
    return {
        status: "passed",
        detail: "Loaded the current new Vault recommendations without enabling a remote connection.",
        observations,
    };
}

export function inspectCompatibilityReview(input: {
    readonly migration: SettingsMigrationState | undefined;
    readonly reviewInitialised: boolean;
    readonly pendingPause: CompatibilityPause | undefined;
}): ReviewHarnessScenarioResult {
    if (input.migration?.requiresSyncReview && !input.reviewInitialised) {
        return {
            status: "failed",
            detail: "The settings migration requires review, but the compatibility review was not initialised.",
            observations: [],
        };
    }

    const observations = [
        `migrationReviewRequired=${String(input.migration?.requiresSyncReview ?? false)}`,
        `compatibilityReviewInitialised=${String(input.reviewInitialised)}`,
        `compatibilityPausePending=${String(input.pendingPause !== undefined)}`,
    ];
    if (input.pendingPause) {
        observations.push(`reasonSources=${input.pendingPause.reasons.map(({ source }) => source).join(",")}`);
        observations.push(`reasonCount=${input.pendingPause.reasons.length}`);
        observations.push(`resumable=${String(input.pendingPause.resumable)}`);
    }
    return {
        status: "passed",
        detail: input.pendingPause
            ? "A device-local compatibility review is pending."
            : "No compatibility review is pending on this device.",
        observations,
    };
}

export interface ReviewHarnessReportScenario {
    readonly id: ReviewHarnessScenarioId;
    readonly title: string;
    readonly mode: ReviewHarnessScenarioMode;
    readonly status: ReviewHarnessScenarioStatus;
    readonly detail: string;
}

export interface ReviewHarnessReportInput {
    readonly generatedAt: string;
    readonly environment: {
        readonly pluginVersion: string;
        readonly obsidianVersion: string;
        readonly platform: string;
        readonly userAgent: string;
        readonly viewport: string;
    };
    readonly scenarios: readonly ReviewHarnessReportScenario[];
    readonly transcript: readonly {
        readonly at: string;
        readonly event: string;
        readonly detail?: string;
    }[];
}

function tableCell(value: string): string {
    return value.replace(/\|/gu, "\\|").replace(/\r?\n/gu, "<br>");
}

function table(headers: readonly string[], rows: readonly (readonly string[])[]): string {
    const header = `| ${headers.map(tableCell).join(" | ")} |`;
    const separator = `| ${headers.map(() => "---").join(" | ")} |`;
    const body = rows.map((row) => `| ${row.map(tableCell).join(" | ")} |`);
    return [header, separator, ...body].join("\n");
}

export function formatReviewHarnessReport(input: ReviewHarnessReportInput): string {
    const environment = table(
        ["Field", "Value"],
        [
            ["Plug-in", input.environment.pluginVersion],
            ["Obsidian", input.environment.obsidianVersion],
            ["Platform", input.environment.platform],
            ["User agent", input.environment.userAgent],
            ["Viewport", input.environment.viewport],
        ]
    );
    const scenarios = table(
        ["Scenario", "Mode", "Status", "Detail"],
        input.scenarios.map(({ id, title, mode, status, detail }) => [
            `${title} (${id})`,
            mode,
            status,
            detail,
        ])
    );
    return `## Self-hosted LiveSync Review Harness report

Generated at \`${tableCell(input.generatedAt)}\`.

### Environment

${environment}

### Scenarios

${scenarios}

<details>
<summary>Event transcript</summary>

\`\`\`json
${JSON.stringify(input.transcript, null, 2)}
\`\`\`
</details>

This report was copied locally and was not transmitted by Self-hosted LiveSync. It intentionally omits Vault identifiers, paths, file names, file contents, remote configuration, and secrets. Review the environment information before posting because a user agent and viewport may identify the device or operating system.
`;
}
