import { env } from "@/lib/env";
import { logError, logInfo } from "@/lib/logger";

export interface UpdotMemberSession {
  kc_session: string;
  kc_session_id: string;
}

export class UpdotCoreAuthManager {
  private static instance: UpdotCoreAuthManager;
  private baseUrl: string;
  private currentSession: UpdotMemberSession | null = null;
  private rotationInterval: NodeJS.Timeout | null = null;
  private accountId: string;
  private contactId: string;
  private email: string;
  private password: string;
  /** In-flight login shared by concurrent callers so parallel campaigns log in once. */
  private loginInFlight: Promise<UpdotMemberSession | null> | null = null;

  private constructor() {
    this.baseUrl = env.GetString("UPDOT_CORE_BASE_URL");
    this.accountId = env.GetString("UPDOT_MEMBER_CORE_ACCOUNT_ID");
    this.contactId = env.GetString("UPDOT_MEMBER_CORE_CONTACT_ID");
    this.email = env.GetString("UPDOT_MEMBER_CORE_EMAIL");
    this.password = env.GetString("UPDOT_MEMBER_CORE_PASSWORD");
  }

  public static getInstance(): UpdotCoreAuthManager {
    if (!UpdotCoreAuthManager.instance) {
      UpdotCoreAuthManager.instance = new UpdotCoreAuthManager();
    }
    return UpdotCoreAuthManager.instance;
  }

  public getSession(): UpdotMemberSession | null {
    return this.currentSession;
  }

  public invalidate() {
    this.currentSession = null;
  }

  /**
   * Drop the session only if it is still the one the caller was using.
   *
   * With several campaigns sending at once, one of them can hit a 401 on a session
   * another has already replaced; a blind invalidate would throw away the good
   * session and force everyone to re-login.
   */
  public invalidateIfCurrent(session: UpdotMemberSession | null) {
    if (!session) return;
    if (
      this.currentSession &&
      this.currentSession.kc_session === session.kc_session &&
      this.currentSession.kc_session_id === session.kc_session_id
    ) {
      this.currentSession = null;
    }
  }

  public getAccountInfo() {
    return {
      accountId: this.accountId,
      contactId: this.contactId,
    };
  }

  /**
   * Helper to get active session or perform a login using ENV credentials
   */
  public async getOrLogin(): Promise<UpdotMemberSession | null> {
    if (this.currentSession) return this.currentSession;

    if (!this.email || !this.password) {
      console.error(
        "[UpdotCoreMemberAuth] Missing UPDOT_ADMIN_CORE_EMAIL or UPDOT_ADMIN_CORE_PASSWORD in ENV",
      );
      return null;
    }

    // Concurrent campaigns all reaching for a session share one login round trip
    // instead of racing three logins that invalidate each other upstream.
    if (this.loginInFlight) return this.loginInFlight;

    this.loginInFlight = this.loginAndRotate({
      email: this.email,
      password: this.password,
    }).finally(() => {
      this.loginInFlight = null;
    });

    return this.loginInFlight;
  }

  private extractCookies(
    setCookieHeaders: string[],
    currentCookies: Record<string, string>,
  ) {
    if (!setCookieHeaders) return;
    for (const cookieStr of setCookieHeaders) {
      const primary = cookieStr.split(";")[0];
      const eqIndex = primary.indexOf("=");
      if (eqIndex > 0) {
        const key = primary.substring(0, eqIndex).trim();
        const value = primary.substring(eqIndex + 1).trim();
        currentCookies[key] = value;
      }
    }
  }

  public async loginAndRotate(credentials: {
    email: string;
    password: string;
  }): Promise<UpdotMemberSession | null> {
    try {
      const currentCookies: Record<string, string> = {};

      const emailUrl = `${this.baseUrl}/v2/member/authorization/email`;
      const emailRes = await fetch(emailUrl, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ email: this.email }),
      });

      if (!emailRes.ok) {
        throw new Error(
          `Email Auth Failed (${emailRes.status}): ${await emailRes.text()}`,
        );
      }
      this.extractCookies(emailRes.headers.getSetCookie(), currentCookies);

      const cookies1 = Object.entries(currentCookies)
        .map(([k, v]) => `${k}=${v}`)
        .join("; ");

      const pwdUrl = `${this.baseUrl}/v2/member/authentication/password`;
      const pwdRes = await fetch(pwdUrl, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Cookie: cookies1,
        },
        body: JSON.stringify({ password: this.password }),
      });


      if (!pwdRes.ok) {
        throw new Error(
          `Password Auth Failed (${pwdRes.status}): ${await pwdRes.text()}`,
        );
      }
      this.extractCookies(pwdRes.headers.getSetCookie(), currentCookies);

      const cookies2 = Object.entries(currentCookies)
        .map(([k, v]) => `${k}=${v}`)
        .join("; ");

      const accountBody = {
        accountId: this.accountId,
        contactId: this.contactId,
        fcmToken: "",
        deviceType: "",
        applicationVersion: "0.6.0",
        ipAddress: "65.21.44.12",
        applicationType: "Karma Web",
        os: "linux",
        browser: "chrome",
        location: "some location",
      };
      const accUrl = `${this.baseUrl}/v2/member/access/account`;
      const accRes = await fetch(accUrl, {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Cookie: cookies2,
        },
        body: JSON.stringify(accountBody),
      });
      

      if (!accRes.ok) {
        logInfo(`[UpdotCoreMemberAuth] Access Account returned ${accRes.status}`);
      }
      this.extractCookies(accRes.headers.getSetCookie(), currentCookies);

      const kc_session = currentCookies.k_session || "";
      const kc_session_id = currentCookies.k_session_id || "";

      if (kc_session && kc_session_id) {
        this.currentSession = { kc_session, kc_session_id };
        return this.currentSession;
      }

      console.error(
        `[UpdotCoreMemberAuth] Failed to extract session from cookies:`,
        currentCookies,
      );
      return null;
    } catch (err) {
      logError(`[UpdotCoreMemberAuth] Login Error: ${err}`);
      return null;
    }
  }

  public async rotateSession(): Promise<UpdotMemberSession | null> {
    if (!this.currentSession) {
      return this.getOrLogin();
    }

    try {
      const url = `${this.baseUrl}/v2/member/session`;

      const cookies = [
        `k_session=${this.currentSession.kc_session}`,
        `k_session_id=${this.currentSession.kc_session_id}`,
      ].join("; ");

      const response = await fetch(url, {
        method: "PATCH",
        headers: {
          "Content-Type": "application/json",
          "x-account": this.accountId,
          "x-contact": this.contactId,
          Cookie: cookies,
        },
        body: JSON.stringify({
          application_version: "v0.4.7",
          fcm_token: "avalidfcmtoken",
          ip_address: "17.0.0.1",
          location: "Bengaluru, KA, India",
        }),
      });

      if (!response.ok) {
        const errText = await response.text();
        logError(
          `[UpdotCoreMemberAuth] Rotation failed (${response.status}): ${errText}`,
        );
        this.invalidate();
        return this.getOrLogin();
      }

      const currentCookies: Record<string, string> = {
        kc_session: this.currentSession.kc_session,
        kc_session_id: this.currentSession.kc_session_id,
      };
      this.extractCookies(response.headers.getSetCookie(), currentCookies);

      const kc_session = currentCookies.k_session || "";
      const kc_session_id = currentCookies.k_session_id || "";

      if (kc_session && kc_session_id) {
        this.currentSession = { kc_session, kc_session_id };
        return this.currentSession;
      }

      return this.currentSession;
    } catch (err) {
      logError(`[UpdotCoreMemberAuth] Rotation Error: ${err}`);
      this.invalidate();
      return this.getOrLogin();
    }
  }

  public startRotationWorker(intervalMs: number = 4 * 60 * 1000) {
    if (this.rotationInterval) {
      clearInterval(this.rotationInterval);
    }

    this.getOrLogin().catch((err) =>
      logError(`[UpdotCoreMemberAuth] Initial worker login failed: ${err}`),
    );

    this.rotationInterval = setInterval(async () => {
      await this.rotateSession();
    }, intervalMs);
  }

  public stopRotationWorker() {
    if (this.rotationInterval) {
      clearInterval(this.rotationInterval);
      this.rotationInterval = null;
    }
  }
}
