import { ChallengeRequiredError } from "./browserErrors.js";
import { DirectRefreshParseError } from "./directRefresh.js";
import { HttpStatusError, StorePausedError } from "./httpErrors.js";
import { canonicalize, OfferSchema } from "./types.js";
import type {
  FetchContext,
  OfferRefreshResponse,
  OfferRefreshTarget,
  StoreAdapter,
} from "./types.js";

export async function refreshOffer(
  adapter: StoreAdapter,
  target: OfferRefreshTarget,
  ctx: FetchContext,
): Promise<OfferRefreshResponse> {
  if (!adapter.refreshOffer) return failure(adapter.id, "unsupported");
  try {
    const result = await withTimeout(
      adapter.refreshOffer(target, ctx),
      adapter.directRefreshTimeoutMs ?? adapter.timeoutMs,
      adapter.id,
    );
    if (result.status === "not_found") {
      return { store: adapter.id, ok: true, status: "not_found", checkedAt: Date.now() };
    }
    const parsed = OfferSchema.safeParse(result.offer);
    if (!parsed.success) return failure(adapter.id, "parse_failed");
    const offer = parsed.data;
    const partMatches = canonicalize(offer.partNumber) === canonicalize(target.expectedPartNumber);
    const brandMatches = target.expectedBrand === null ||
      (offer.brand !== null && canonicalize(offer.brand) === canonicalize(target.expectedBrand));
    if (!partMatches || !brandMatches) return failure(adapter.id, "identity_mismatch");
    return { store: adapter.id, ok: true, status: "found", offer, checkedAt: Date.now() };
  } catch (error) {
    if (error instanceof ChallengeRequiredError) {
      return {
        ...failure(adapter.id, "challenge_required"),
        needsSolve: true,
        solveUrl: error.solveUrl,
      };
    }
    if (error instanceof StorePausedError) return failure(adapter.id, "store_paused");
    if (error instanceof RefreshTimeoutError) return failure(adapter.id, "timeout");
    if (error instanceof DirectRefreshParseError) return failure(adapter.id, "parse_failed");
    if (error instanceof HttpStatusError) return failure(adapter.id, "fetch_failed");
    return failure(adapter.id, "fetch_failed");
  }
}

function failure(store: string, errorCode: OfferRefreshResponse["errorCode"]): OfferRefreshResponse {
  return { store, ok: false, status: "error", checkedAt: null, errorCode };
}

class RefreshTimeoutError extends Error {
  constructor(storeId: string) {
    super(`${storeId}: direct refresh timed out`);
  }
}

function withTimeout<T>(promise: Promise<T>, ms: number, storeId: string): Promise<T> {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => reject(new RefreshTimeoutError(storeId)), ms);
    promise.then(
      (value) => { clearTimeout(timer); resolve(value); },
      (error) => { clearTimeout(timer); reject(error); },
    );
  });
}
