import type { FactoryProvider, ProviderObservation, ReconcileRequest } from "@awp/application";
import {
  unsafeOpaqueId,
  type FactoryRunId,
  type PlanRevisionId,
  type ProviderDescriptor,
  type ProviderError,
  type ProviderId,
  type ProviderOperationContext,
  type ProviderReference,
  type ProviderResult,
} from "@awp/contracts";

export const FABRO_PROVIDER_ID = unsafeOpaqueId<ProviderId>("provider:fabro");
export const FABRO_SOURCE_PROOF = Object.freeze({
  repository: "fabro-sh/fabro",
  reviewedAt: "2026-08-20",
  sourceRevision: "03c3412e513b845c5acc992b30dede3a34dc6858",
  interface: "REST API + durable run/event/checkpoint model",
  adapterVersion: "awp-i0-fabro-2026-08-20",
} as const);

import type { FabroNativeClient, FabroNativeRun } from "./native.js";

export class FabroAdapterError extends Error {
  constructor(
    readonly providerError: ProviderError,
    options?: ErrorOptions,
  ) {
    super(providerError.safeMessage, options);
    this.name = "FabroAdapterError";
  }
}

function observedNow(): string {
  return new Date().toISOString();
}

function normalizeError(error: unknown): FabroAdapterError {
  if (error instanceof FabroAdapterError) return error;
  const status =
    typeof error === "object" && error !== null && "status" in error
      ? Number((error as { readonly status?: unknown }).status)
      : undefined;
  const category =
    status === 401
      ? "auth"
      : status === 403
        ? "permission"
        : status === 404
          ? "missing-resource"
          : status === 409
            ? "conflict-stale"
            : status === 429
              ? "rate-capacity"
              : status !== undefined && status >= 500
                ? "unavailable"
                : "adapter-protocol";
  return new FabroAdapterError(
    {
      category,
      retryable: category === "unavailable" || category === "rate-capacity",
      providerId: FABRO_PROVIDER_ID,
      safeMessage: `Fabro provider operation failed (${category})`,
      observedAt: observedNow(),
    },
    { cause: error },
  );
}

function fail(category: ProviderError["category"], safeMessage: string): never {
  throw new FabroAdapterError({
    category,
    retryable: category === "unavailable" || category === "rate-capacity",
    providerId: FABRO_PROVIDER_ID,
    safeMessage,
    observedAt: observedNow(),
  });
}

function reference(run: FabroNativeRun, observedAt: string): ProviderReference {
  return {
    providerId: FABRO_PROVIDER_ID,
    resourceType: "factory-run",
    nativeId: run.id,
    nativeRevision: `${run.status}:${run.checkpointId ?? "none"}`,
    ...(run.url === undefined ? {} : { url: run.url }),
    observedAt,
  };
}

function observation(run: FabroNativeRun): ProviderObservation {
  return {
    state: run.status,
    details: Object.freeze({
      factoryRunId: run.factoryRunId,
      planRevisionId: run.planRevisionId,
      ...(run.currentStage === undefined ? {} : { currentStage: run.currentStage }),
      ...(run.checkpointId === undefined ? {} : { checkpointId: run.checkpointId }),
      ...(run.failureCode === undefined ? {} : { failureCode: run.failureCode }),
    }),
  };
}

function result(run: FabroNativeRun): ProviderResult<ProviderObservation> {
  const observedAt = observedNow();
  return {
    value: observation(run),
    references: [reference(run, observedAt)],
    observedAt,
    reconciliationToken: `${run.id}:${run.status}:${run.checkpointId ?? "none"}`,
  };
}

export class FabroFactoryProvider implements FactoryProvider {
  constructor(private readonly client: FabroNativeClient) {}

  async describe(): Promise<ProviderDescriptor> {
    return {
      providerId: FABRO_PROVIDER_ID,
      kind: "factory-workflow",
      adapterVersion: FABRO_SOURCE_PROOF.adapterVersion,
      capabilities: [
        "factory.start",
        "factory.cancel",
        "factory.reconcile",
        "factory.checkpoint-observe",
      ],
      authModes: ["credential-reference"],
      resourceTypes: ["factory-run", "checkpoint", "stage"],
      healthFeatures: ["run-status", "failure-code", "checkpoint"],
    };
  }

  async start(
    context: ProviderOperationContext,
    factoryRunId: FactoryRunId,
    planRevisionId: PlanRevisionId,
  ): Promise<ProviderResult<ProviderObservation>> {
    try {
      const existing = await this.client.findRunByIdempotencyKey(context.idempotencyKey);
      if (existing !== undefined) {
        if (
          existing.factoryRunId !== String(factoryRunId) ||
          existing.planRevisionId !== String(planRevisionId)
        ) {
          fail("conflict-stale", "Fabro idempotency key is already bound to another run input");
        }
        return result(existing);
      }
      return result(
        await this.client.startRun({
          factoryRunId: String(factoryRunId),
          planRevisionId: String(planRevisionId),
          idempotencyKey: context.idempotencyKey,
        }),
      );
    } catch (error) {
      throw normalizeError(error);
    }
  }

  async cancel(
    context: ProviderOperationContext,
    factoryRunId: FactoryRunId,
  ): Promise<ProviderResult<ProviderObservation>> {
    void context;
    try {
      const run = await this.client.findRunByFactoryRunId(String(factoryRunId));
      if (run === undefined) fail("missing-resource", "Fabro run does not exist");
      if (run.status === "completed" || run.status === "failed" || run.status === "cancelled") {
        return result(run);
      }
      return result(await this.client.cancelRun(run.id));
    } catch (error) {
      throw normalizeError(error);
    }
  }

  async reconcile(request: ReconcileRequest): Promise<ProviderResult<ProviderObservation>> {
    try {
      if (request.reference.providerId !== FABRO_PROVIDER_ID) {
        fail("adapter-protocol", "Cannot reconcile a reference owned by another provider");
      }
      const run = await this.client.getRun(request.reference.nativeId);
      if (run === undefined) fail("missing-resource", "Fabro run no longer exists");
      return result(run);
    } catch (error) {
      throw normalizeError(error);
    }
  }
}

export { FabroHttpClient } from "./http-client.js";
export * from "./native.js";
