import { logError, logInfo } from "@/lib/logger";
import { UpdotAuthManager } from "@/lib/updot-auth";
import type {
  CreatePromoCodeRequest,
  ListPromoCodesQuery,
  UpdatePromoCodeRequest,
} from "./promo-codes.schema";

// Recursively drop null/undefined keys. Updot's promo-code API rejects null for
// optional string fields (rewards[].name/currency/expiresAt, etc.) — omitting
// them is the only valid form, so strip before forwarding.
function stripNullish<T>(value: T): T {
  if (Array.isArray(value)) {
    return value.map((v) => stripNullish(v)) as unknown as T;
  }
  if (value && typeof value === "object") {
    const out: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(value)) {
      if (v === null || v === undefined) continue;
      out[k] = stripNullish(v);
    }
    return out as T;
  }
  return value;
}

export class PromoCodesService {
  private static instance: PromoCodesService | null = null;
  private externalApiUrl: string;

  private constructor(externalApiUrl?: string) {
    this.externalApiUrl =
      externalApiUrl ??
      process.env.UPDOT_CORE_BASE_URL ??
      "https://uat.core.karmagroup.com";
  }

  static getInstance(externalApiUrl?: string) {
    if (!PromoCodesService.instance) {
      PromoCodesService.instance = new PromoCodesService(externalApiUrl);
    }
    return PromoCodesService.instance;
  }

  private async parseResponse(res: Response, context: string) {
    const contentType = res.headers.get("content-type") || "";
    const raw = await res.text();
    const parsed =
      contentType.includes("application/json") && raw
        ? (() => {
          try {
            return JSON.parse(raw);
          } catch {
            return raw;
          }
        })()
        : raw;

    if (!res.ok) {
      logError(`${context}: Upstream failed (${res.status})`, parsed);
      const err: any = new Error(
        typeof parsed === "string"
          ? parsed
          : parsed?.message || `Upstream failed with ${res.status}`,
      );
      err.status = res.status;
      err.detail = parsed;
      throw err;
    }

    return parsed;
  }

  private async getSession() {
    const authManager = UpdotAuthManager.getInstance();
    const session = await authManager.getOrLogin();
    if (!session) {
      throw new Error("Unable to obtain Updot core authentication session.");
    }
    return session;
  }

  private getHeaders(session: any) {
    const cookies = [
      `admin_session=${session.admin_session}`,
      `admin_session_id=${session.admin_session_id}`,
      `__sstkn=${session.admin_session}`,
      `__ssid=${session.admin_session_id}`,
      `kc_session=${session.admin_session}`,
      `kc_session_id=${session.admin_session_id}`,
      session.admin_scopes ? `admin_scopes=${session.admin_scopes}` : "",
    ]
      .filter(Boolean)
      .join("; ");
    return {
      "Content-Type": "application/json",
      Cookie: cookies,
    };
  }

  private async fetchWithAuthRetry(
    endpoint: string,
    init: { method: string; body?: string },
  ): Promise<Response> {
    let session = await this.getSession();
    let res = await fetch(endpoint, {
      ...init,
      headers: this.getHeaders(session),
    });
    const retryStatuses = ["GET", "HEAD"].includes(init.method)
      ? [400, 401, 403]
      : [401, 403];
    if (retryStatuses.includes(res.status)) {
      logInfo(
        `PromoCodesService: upstream ${res.status} on ${init.method} ${endpoint} — refreshing Updot session and retrying`,
      );
      UpdotAuthManager.getInstance().invalidate();
      session = await this.getSession();
      res = await fetch(endpoint, {
        ...init,
        headers: this.getHeaders(session),
      });
    }
    return res;
  }

  async createPromoCode(
    request: CreatePromoCodeRequest,
  ): Promise<{ success: boolean; data: string; message: string }> {
    try {
      const endpoint = `${this.externalApiUrl}/v1/admin/promo-codes`;
      logInfo(
        `PromoCodesService.createPromoCode: code=${request.code} -> ${endpoint}`,
      );

      const res = await this.fetchWithAuthRetry(endpoint, {
        method: "POST",
        body: JSON.stringify(stripNullish(request)),
      });

      const parsed = await this.parseResponse(
        res,
        "PromoCodesService.createPromoCode",
      );

      return {
        success: true,
        data: parsed,
        message: "Promo code created successfully",
      };
    } catch (e) {
      logError("PromoCodesService.createPromoCode: error", e);
      throw e;
    }
  }

  async updatePromoCode(
    id: string,
    request: UpdatePromoCodeRequest,
  ): Promise<{ success: boolean; data: string; message: string }> {
    try {

      const endpoint = `${this.externalApiUrl}/v1/admin/promo-codes/${id}`;

      const res = await this.fetchWithAuthRetry(endpoint, {
        method: "PATCH",
        body: JSON.stringify(stripNullish(request)),
      });

      const parsed = await this.parseResponse(
        res,
        "PromoCodesService.updatePromoCode",
      );

      return {
        success: true,
        data: parsed,
        message: "Promo code updated successfully",
      };
    } catch (e) {
      logError("PromoCodesService.updatePromoCode: error", e);
      throw e;
    }
  }

  async listPromoCodes(
    query?: ListPromoCodesQuery,
  ): Promise<{ success: boolean; data: string; message: string }> {
    try {
      const endpoint = new URL(`${this.externalApiUrl}/v1/admin/promo-codes`);
      if (typeof query?.isActive === "string" && query.isActive.length > 0) {
        endpoint.searchParams.set("isActive", query.isActive);
      }
      if (
        typeof query?.onlyPromoCodes === "string" &&
        query.onlyPromoCodes.length > 0
      ) {
        endpoint.searchParams.set("onlyPromoCodes", query.onlyPromoCodes);
      }
      if (
        typeof query?.onlyMemberReferralCodes === "string" &&
        query.onlyMemberReferralCodes.length > 0
      ) {
        endpoint.searchParams.set(
          "onlyMemberReferralCodes",
          query.onlyMemberReferralCodes,
        );
      }
      if (
        typeof query?.includeExpired === "string" &&
        query.includeExpired.length > 0
      ) {
        endpoint.searchParams.set("includeExpired", query.includeExpired);
      }
      if (
        typeof query?.searchTerm === "string" &&
        query.searchTerm.length > 0
      ) {
        endpoint.searchParams.set("searchTerm", query.searchTerm);
      }
      if (typeof query?.limit === "string" && query.limit.length > 0) {
        endpoint.searchParams.set("limit", query.limit);
      }
      if (typeof query?.offset === "string" && query.offset.length > 0) {
        endpoint.searchParams.set("offset", query.offset);
      }

      const res = await this.fetchWithAuthRetry(endpoint.toString(), {
        method: "GET",
      });

      const parsed = await this.parseResponse(
        res,
        "PromoCodesService.listPromoCodes",
      );

      return {
        success: true,
        data: parsed?.data ?? parsed,
        message: "Promo codes fetched successfully",
      };
    } catch (e) {
      logError("PromoCodesService.listPromoCodes: error", e);
      throw e;
    }
  }

  async getPromoCodeByCode(
    code: string,
  ): Promise<{ data: any; message: string }> {
    try {
      const endpoint = `${this.externalApiUrl}/v1/admin/promo-codes/${code}`;
      const res = await this.fetchWithAuthRetry(endpoint, {
        method: "GET",
      });

      const parsed = await this.parseResponse(
        res,
        "PromoCodesService.getPromoCodeByCode",
      );

      return {
        data: parsed,
        message: "Promo code retrieved successfully",
      };
    } catch (e) {
      logError("PromoCodesService.getPromoCodeByCode: error", e);
      throw e;
    }
  }

  async deletePromoCode(
    id: string,
  ): Promise<{ success: boolean; data: any; message: string }> {
    try {
      const endpoint = `${this.externalApiUrl}/v1/admin/promo-codes/${id}`;
      logInfo(
        `PromoCodesService.deletePromoCode: id=${id} -> ${endpoint}`,
      );

      const res = await this.fetchWithAuthRetry(endpoint, {
        method: "DELETE",
      });

      const parsed = await this.parseResponse(
        res,
        "PromoCodesService.deletePromoCode",
      );

      return {
        success: true,
        data: parsed,
        message: "Promo code deleted successfully",
      };
    } catch (e) {
      logError("PromoCodesService.deletePromoCode: error", e);
      throw e;
    }
  }
}
