"use client";

import { ApiService } from "@/lib/services/api-service";
import { CacheService, CACHE_KEYS, CACHE_TTL } from "@/lib/services/cache-service";
import type { PkmUpgradeDomainState } from "@/lib/services/personal-knowledge-model-service";

export class PkmUpgradeRouteUnavailableError extends Error {
  status: number;

  constructor(message: string, status: number) {
    super(message);
    this.name = "PkmUpgradeRouteUnavailableError";
    this.status = status;
  }
}

export type PkmUpgradeMode = "real" | "rehearsal_no_write";

export type PkmUpgradeErrorContext = {
  stage: string | null;
  domain: string | null;
  httpStatus: number | null;
  detail: string | null;
  correlationId: string | null;
  requestId: string | null;
  traceId: string | null;
  clientRoute: string | null;
  manifestRoute: string | null;
  mode: PkmUpgradeMode | null;
};

export type PkmUpgradeStep = {
  runId: string;
  domain: string;
  status: string;
  fromDomainContractVersion: number;
  toDomainContractVersion: number;
  fromReadableSummaryVersion: number;
  toReadableSummaryVersion: number;
  attemptCount: number;
  lastCompletedContentRevision: number | null;
  lastCompletedManifestVersion: number | null;
  checkpointPayload: Record<string, unknown>;
  createdAt: string | null;
  updatedAt: string | null;
};

export type PkmUpgradeRun = {
  runId: string;
  userId: string;
  status: string;
  mode: PkmUpgradeMode;
  fromModelVersion: number;
  toModelVersion: number;
  currentDomain: string | null;
  initiatedBy: string;
  resumeCount: number;
  startedAt: string | null;
  lastCheckpointAt: string | null;
  completedAt: string | null;
  lastError: string | null;
  errorContext: PkmUpgradeErrorContext | null;
  createdAt: string | null;
  updatedAt: string | null;
  steps: PkmUpgradeStep[];
};

export type PkmUpgradeStatus = {
  userId: string;
  modelVersion: number;
  storedModelVersion: number;
  effectiveModelVersion: number;
  targetModelVersion: number;
  currentPkmContractVersion: string | null;
  targetPkmContractVersion: string | null;
  currentReadableProjectionVersion: string | null;
  targetReadableProjectionVersion: string | null;
  upgradeStatus: string;
  upgradableDomains: PkmUpgradeDomainState[];
  lastUpgradedAt: string | null;
  run: PkmUpgradeRun | null;
};

function mapErrorContext(
  payload: Record<string, unknown> | null | undefined
): PkmUpgradeErrorContext | null {
  if (!payload || typeof payload !== "object") return null;
  const modeValue = String(payload.mode || "").trim();
  return {
    stage: typeof payload.stage === "string" ? payload.stage : null,
    domain: typeof payload.domain === "string" ? payload.domain : null,
    httpStatus:
      typeof payload.http_status === "number"
        ? payload.http_status
        : typeof payload.httpStatus === "number"
          ? payload.httpStatus
          : null,
    detail: typeof payload.detail === "string" ? payload.detail : null,
    correlationId:
      typeof payload.correlation_id === "string"
        ? payload.correlation_id
        : typeof payload.correlationId === "string"
          ? payload.correlationId
          : null,
    requestId:
      typeof payload.request_id === "string"
        ? payload.request_id
        : typeof payload.requestId === "string"
          ? payload.requestId
          : null,
    traceId:
      typeof payload.trace_id === "string"
        ? payload.trace_id
        : typeof payload.traceId === "string"
          ? payload.traceId
          : null,
    clientRoute:
      typeof payload.client_route === "string"
        ? payload.client_route
        : typeof payload.clientRoute === "string"
          ? payload.clientRoute
          : null,
    manifestRoute:
      typeof payload.manifest_route === "string"
        ? payload.manifest_route
        : typeof payload.manifestRoute === "string"
          ? payload.manifestRoute
          : null,
    mode:
      modeValue === "rehearsal_no_write" || modeValue === "real"
        ? modeValue
        : null,
  };
}

function authHeaders(vaultOwnerToken?: string): HeadersInit {
  return vaultOwnerToken ? { Authorization: `Bearer ${vaultOwnerToken}` } : {};
}

function mapDomain(domain: Record<string, unknown>): PkmUpgradeDomainState {
  const capabilities = domain.capabilities_applied || domain.capabilitiesApplied;
  const blockers = domain.blocked_reasons || domain.blockedReasons;
  return {
    domain: String(domain.domain || ""),
    currentDomainContractVersion: Number(domain.current_domain_contract_version || 1),
    targetDomainContractVersion: Number(domain.target_domain_contract_version || 1),
    currentReadableSummaryVersion: Number(domain.current_readable_summary_version || 0),
    targetReadableSummaryVersion: Number(domain.target_readable_summary_version || 0),
    currentPkmContractVersion:
      typeof domain.current_pkm_contract_version === "string"
        ? domain.current_pkm_contract_version
        : typeof domain.currentPkmContractVersion === "string"
          ? domain.currentPkmContractVersion
          : null,
    targetPkmContractVersion:
      typeof domain.target_pkm_contract_version === "string"
        ? domain.target_pkm_contract_version
        : typeof domain.targetPkmContractVersion === "string"
          ? domain.targetPkmContractVersion
          : null,
    currentReadableProjectionVersion:
      typeof domain.current_readable_projection_version === "string"
        ? domain.current_readable_projection_version
        : typeof domain.currentReadableProjectionVersion === "string"
          ? domain.currentReadableProjectionVersion
          : null,
    targetReadableProjectionVersion:
      typeof domain.target_readable_projection_version === "string"
        ? domain.target_readable_projection_version
        : typeof domain.targetReadableProjectionVersion === "string"
          ? domain.targetReadableProjectionVersion
          : null,
    capabilitiesApplied: Array.isArray(capabilities) ? capabilities.map(String) : [],
    blockedReasons: Array.isArray(blockers) ? blockers.map(String) : [],
    upgradedAt: typeof domain.upgraded_at === "string" ? domain.upgraded_at : null,
    needsUpgrade: Boolean(domain.needs_upgrade),
  };
}

function mapStep(step: Record<string, unknown>): PkmUpgradeStep {
  return {
    runId: String(step.run_id || ""),
    domain: String(step.domain || ""),
    status: String(step.status || "pending"),
    fromDomainContractVersion: Number(step.from_domain_contract_version || 1),
    toDomainContractVersion: Number(step.to_domain_contract_version || 1),
    fromReadableSummaryVersion: Number(step.from_readable_summary_version || 0),
    toReadableSummaryVersion: Number(step.to_readable_summary_version || 0),
    attemptCount: Number(step.attempt_count || 0),
    lastCompletedContentRevision:
      typeof step.last_completed_content_revision === "number"
        ? step.last_completed_content_revision
        : null,
    lastCompletedManifestVersion:
      typeof step.last_completed_manifest_version === "number"
        ? step.last_completed_manifest_version
        : null,
    checkpointPayload:
      step.checkpoint_payload && typeof step.checkpoint_payload === "object"
        ? (step.checkpoint_payload as Record<string, unknown>)
        : {},
    createdAt: typeof step.created_at === "string" ? step.created_at : null,
    updatedAt: typeof step.updated_at === "string" ? step.updated_at : null,
  };
}

function mapRun(run: Record<string, unknown> | null | undefined): PkmUpgradeRun | null {
  if (!run || typeof run !== "object") return null;
  return {
    runId: String(run.run_id || ""),
    userId: String(run.user_id || ""),
    status: String(run.status || "planned"),
    mode:
      String(run.mode || "real").trim() === "rehearsal_no_write"
        ? "rehearsal_no_write"
        : "real",
    fromModelVersion: Number(run.from_model_version || 1),
    toModelVersion: Number(run.to_model_version || 1),
    currentDomain: typeof run.current_domain === "string" ? run.current_domain : null,
    initiatedBy: String(run.initiated_by || "unlock_warm"),
    resumeCount: Number(run.resume_count || 0),
    startedAt: typeof run.started_at === "string" ? run.started_at : null,
    lastCheckpointAt:
      typeof run.last_checkpoint_at === "string" ? run.last_checkpoint_at : null,
    completedAt: typeof run.completed_at === "string" ? run.completed_at : null,
    lastError: typeof run.last_error === "string" ? run.last_error : null,
    errorContext: mapErrorContext(
      run.error_context && typeof run.error_context === "object"
        ? (run.error_context as Record<string, unknown>)
        : null
    ),
    createdAt: typeof run.created_at === "string" ? run.created_at : null,
    updatedAt: typeof run.updated_at === "string" ? run.updated_at : null,
    steps: Array.isArray(run.steps)
      ? run.steps
          .filter((step): step is Record<string, unknown> => !!step && typeof step === "object")
          .map(mapStep)
      : [],
  };
}

function mapStatus(payload: Record<string, unknown>): PkmUpgradeStatus {
  return {
    userId: String(payload.user_id || ""),
    modelVersion: Number(payload.model_version || 1),
    storedModelVersion: Number(payload.stored_model_version || payload.model_version || 1),
    effectiveModelVersion: Number(
      payload.effective_model_version || payload.model_version || 1
    ),
    targetModelVersion: Number(payload.target_model_version || 1),
    currentPkmContractVersion:
      typeof payload.current_pkm_contract_version === "string"
        ? payload.current_pkm_contract_version
        : typeof payload.currentPkmContractVersion === "string"
          ? payload.currentPkmContractVersion
          : null,
    targetPkmContractVersion:
      typeof payload.target_pkm_contract_version === "string"
        ? payload.target_pkm_contract_version
        : typeof payload.targetPkmContractVersion === "string"
          ? payload.targetPkmContractVersion
          : null,
    currentReadableProjectionVersion:
      typeof payload.current_readable_projection_version === "string"
        ? payload.current_readable_projection_version
        : typeof payload.currentReadableProjectionVersion === "string"
          ? payload.currentReadableProjectionVersion
          : null,
    targetReadableProjectionVersion:
      typeof payload.target_readable_projection_version === "string"
        ? payload.target_readable_projection_version
        : typeof payload.targetReadableProjectionVersion === "string"
          ? payload.targetReadableProjectionVersion
          : null,
    upgradeStatus: String(payload.upgrade_status || "current"),
    upgradableDomains: Array.isArray(payload.upgradable_domains)
      ? payload.upgradable_domains
          .filter((domain): domain is Record<string, unknown> => !!domain && typeof domain === "object")
          .map(mapDomain)
      : [],
    lastUpgradedAt:
      typeof payload.last_upgraded_at === "string" ? payload.last_upgraded_at : null,
    run: mapRun(
      payload.run && typeof payload.run === "object"
        ? (payload.run as Record<string, unknown>)
        : null
    ),
  };
}

async function requestStatus(
  url: string,
  options: RequestInit,
  context: string
): Promise<PkmUpgradeStatus> {
  const response = await ApiService.apiFetch(url, options);
  if (!response.ok) {
    const detail = await response.text();
    if (response.status === 404 && url.includes("/api/pkm/upgrade")) {
      throw new PkmUpgradeRouteUnavailableError(
        `${context}: ${response.status}${detail ? ` - ${detail}` : ""}`,
        response.status
      );
    }
    throw new Error(`${context}: ${response.status}${detail ? ` - ${detail}` : ""}`);
  }
  return mapStatus((await response.json()) as Record<string, unknown>);
}

export class PkmUpgradeService {
  private static readonly API_PREFIX = "/api/pkm/upgrade";
  private static readonly inflight = new Map<string, Promise<PkmUpgradeStatus>>();

  private static getCacheKey(userId: string): string {
    return CACHE_KEYS.PKM_UPGRADE_STATUS(userId);
  }

  private static invalidateStatus(userId: string): void {
    const cacheKey = this.getCacheKey(userId);
    CacheService.getInstance().invalidate(cacheKey);
    this.inflight.delete(cacheKey);
  }

  static async getStatus(params: {
    userId: string;
    vaultOwnerToken?: string;
    force?: boolean;
  }): Promise<PkmUpgradeStatus> {
    const cacheKey = this.getCacheKey(params.userId);
    if (!params.force) {
      const cached = CacheService.getInstance().get<PkmUpgradeStatus>(cacheKey);
      if (cached) {
        return cached;
      }
      const inflight = this.inflight.get(cacheKey);
      if (inflight) {
        return inflight;
      }
    }

    const request = requestStatus(
      `${this.API_PREFIX}/status/${params.userId}`,
      {
        headers: authHeaders(params.vaultOwnerToken),
      },
      "Failed to get PKM upgrade status"
    ).then((payload) => {
      CacheService.getInstance().set(cacheKey, payload, CACHE_TTL.SHORT);
      return payload;
    });

    this.inflight.set(cacheKey, request);
    return request.finally(() => {
      if (this.inflight.get(cacheKey) === request) {
        this.inflight.delete(cacheKey);
      }
    });
  }

  static async startOrResume(params: {
    userId: string;
    vaultOwnerToken?: string;
    initiatedBy?: string;
    mode?: PkmUpgradeMode;
  }): Promise<PkmUpgradeStatus> {
    this.invalidateStatus(params.userId);
    return requestStatus(
      `${this.API_PREFIX}/start-or-resume`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...authHeaders(params.vaultOwnerToken),
        },
        body: JSON.stringify({
          user_id: params.userId,
          initiated_by: params.initiatedBy || "unlock_warm",
          mode: params.mode || "real",
        }),
      },
      "Failed to start or resume PKM upgrade"
    ).then((payload) => {
      CacheService.getInstance().set(this.getCacheKey(params.userId), payload, CACHE_TTL.SHORT);
      return payload;
    });
  }

  static async updateRunStatus(params: {
    runId: string;
    userId: string;
    status: string;
    currentDomain?: string | null;
    lastError?: string | null;
    errorContext?: Record<string, unknown> | null;
    vaultOwnerToken?: string;
  }): Promise<PkmUpgradeStatus> {
    this.invalidateStatus(params.userId);
    return requestStatus(
      `${this.API_PREFIX}/runs/${params.runId}/status`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...authHeaders(params.vaultOwnerToken),
        },
        body: JSON.stringify({
          user_id: params.userId,
          status: params.status,
          current_domain: params.currentDomain || null,
          last_error: params.lastError || null,
          error_context: params.errorContext || null,
        }),
      },
      "Failed to update PKM upgrade run"
    ).then((payload) => {
      CacheService.getInstance().set(this.getCacheKey(params.userId), payload, CACHE_TTL.SHORT);
      return payload;
    });
  }

  static async updateStep(params: {
    runId: string;
    domain: string;
    userId: string;
    status: string;
    checkpointPayload?: Record<string, unknown>;
    attemptCount?: number;
    lastCompletedContentRevision?: number;
    lastCompletedManifestVersion?: number;
    vaultOwnerToken?: string;
  }): Promise<PkmUpgradeStatus> {
    this.invalidateStatus(params.userId);
    return requestStatus(
      `${this.API_PREFIX}/runs/${params.runId}/steps/${encodeURIComponent(params.domain)}`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...authHeaders(params.vaultOwnerToken),
        },
        body: JSON.stringify({
          user_id: params.userId,
          status: params.status,
          checkpoint_payload: params.checkpointPayload || {},
          attempt_count: params.attemptCount,
          last_completed_content_revision: params.lastCompletedContentRevision,
          last_completed_manifest_version: params.lastCompletedManifestVersion,
        }),
      },
      "Failed to update PKM upgrade step"
    ).then((payload) => {
      CacheService.getInstance().set(this.getCacheKey(params.userId), payload, CACHE_TTL.SHORT);
      return payload;
    });
  }

  static async completeRun(params: {
    runId: string;
    userId: string;
    vaultOwnerToken?: string;
  }): Promise<PkmUpgradeStatus> {
    this.invalidateStatus(params.userId);
    return requestStatus(
      `${this.API_PREFIX}/runs/${params.runId}/complete`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...authHeaders(params.vaultOwnerToken),
        },
        body: JSON.stringify({
          user_id: params.userId,
        }),
      },
      "Failed to complete PKM upgrade run"
    ).then((payload) => {
      CacheService.getInstance().set(this.getCacheKey(params.userId), payload, CACHE_TTL.SHORT);
      return payload;
    });
  }

  static async failRun(params: {
    runId: string;
    userId: string;
    lastError?: string | null;
    errorContext?: Record<string, unknown> | null;
    vaultOwnerToken?: string;
  }): Promise<PkmUpgradeStatus> {
    this.invalidateStatus(params.userId);
    return requestStatus(
      `${this.API_PREFIX}/runs/${params.runId}/fail`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          ...authHeaders(params.vaultOwnerToken),
        },
        body: JSON.stringify({
          user_id: params.userId,
          status: "failed",
          last_error: params.lastError || null,
          error_context: params.errorContext || null,
        }),
      },
      "Failed to fail PKM upgrade run"
    ).then((payload) => {
      CacheService.getInstance().set(this.getCacheKey(params.userId), payload, CACHE_TTL.SHORT);
      return payload;
    });
  }
}
