import { shopifyApi, ApiVersion } from "@shopify/shopify-api";
import "@shopify/shopify-api/adapters/node";
import crypto from "crypto";
import { EncryptedMultiTenantSessionStorage } from "./lib/shopify/session-storage";
import { getTenantByDomain } from "./lib/security/tenant-context";
import {
  registerShop,
  getShopWithDecryptedToken,
  exchangeSessionTokenForAccessToken,
} from "./services/shop.server";

export const shopify = shopifyApi({
  apiKey: process.env.SHOPIFY_API_KEY || "ff484160a48beda29e79144ee4e9a819",
  apiSecretKey: process.env.SHOPIFY_API_SECRET || "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",
  scopes: process.env.SCOPES?.split(",") || [
    "read_products",
    "read_content",
    "read_customers",
    "read_orders",
    "read_discounts",
  ],
  hostName: (process.env.SHOPIFY_APP_URL || "https://portal.webiletechnologies.com")
    .replace(/^https?:\/\//, "")
    .replace(/\/$/, ""),
  apiVersion: ApiVersion.April25,
  isEmbeddedApp: true,
  future: {
    unstable_managedPricingSupport: true,
    lineItemBilling: true,
  },
});

export const sessionStorage = new EncryptedMultiTenantSessionStorage();

export function addDocumentResponseHeaders(_request: Request, responseHeaders: Headers) {
  responseHeaders.set("X-Frame-Options", "ALLOWALL");
  responseHeaders.set(
    "Content-Security-Policy",
    "frame-ancestors https://admin.shopify.com https://*.myshopify.com;"
  );
}

export interface VerifiedSessionTokenPayload {
  iss: string;
  dest: string;
  aud: string;
  sub: string;
  exp: number;
  nbf: number;
  iat: number;
  jti: string;
  sid?: string;
  shopDomain: string;
  signatureVerified: boolean;
}

/**
 * Verifies a Shopify App Bridge Session Token (JWT) using HMAC-SHA256 with SHOPIFY_API_SECRET.
 */
export function verifyShopifySessionToken(jwtToken: string): VerifiedSessionTokenPayload | null {
  if (!jwtToken || typeof jwtToken !== "string") return null;
  const cleanToken = jwtToken.replace(/^Bearer\s+/i, "").trim();
  const parts = cleanToken.split(".");
  if (parts.length !== 3) return null;

  const [headerB64, payloadB64, signatureB64] = parts;
  try {
    const payloadJson = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
    const dest = String(payloadJson.dest || payloadJson.iss || "")
      .replace(/^https?:\/\//, "")
      .replace(/\/admin.*$/, "")
      .toLowerCase()
      .trim();

    if (!dest) return null;

    const secret = process.env.SHOPIFY_API_SECRET || "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";
    const expectedSig = crypto
      .createHmac("sha256", secret)
      .update(`${headerB64}.${payloadB64}`)
      .digest("base64url");

    const signatureVerified =
      signatureB64.length === expectedSig.length &&
      crypto.timingSafeEqual(Buffer.from(signatureB64), Buffer.from(expectedSig));

    return {
      ...payloadJson,
      shopDomain: dest,
      signatureVerified,
    };
  } catch {
    return null;
  }
}

/**
 * Extracts a Shopify Session Token (JWT) from Authorization Bearer header or ?id_token query param.
 */
export function extractSessionTokenFromRequest(request: Request): string | null {
  const authHeader = request.headers.get("Authorization");
  if (authHeader && authHeader.startsWith("Bearer ")) {
    const token = authHeader.replace(/^Bearer\s+/i, "").trim();
    if (token.split(".").length === 3) {
      return token;
    }
  }
  try {
    const url = new URL(request.url);
    const idTokenParam = url.searchParams.get("id_token");
    if (idTokenParam && idTokenParam.split(".").length === 3) {
      return idTokenParam.trim();
    }
  } catch {}
  return null;
}

/**
 * Authenticates merchant admin requests using Session Tokens (JWT) + OAuth Token Exchange,
 * and resolves multi-tenant shop context dynamically from the current request.
 */
export async function authenticateAdmin(request: Request) {
  const url = new URL(request.url);
  const shopParam = url.searchParams.get("shop");
  const sessionToken = extractSessionTokenFromRequest(request);
  const verifiedPayload = sessionToken ? verifyShopifySessionToken(sessionToken) : null;

  let shopDomain = verifiedPayload?.shopDomain || shopParam || "";

  if (!shopDomain) {
    throw new Response("Unauthorized: Shopify shop domain or session token is required", {
      status: 401,
      headers: {
        "Content-Type": "text/plain",
        "X-Shopify-Retry-Invalid-Session-Request": "1",
      },
    });
  }

  const normalizedShop = shopDomain.includes(".")
    ? shopDomain.toLowerCase().trim()
    : `${shopDomain.toLowerCase().trim()}.myshopify.com`;

  // Ensure shop record exists and resolve a verified valid access token
  let existingWithToken = await getShopWithDecryptedToken(normalizedShop).catch(() => null);
  let validAccessToken = existingWithToken?.decryptedAccessToken || "";

  const isLegacyNonExpiring = Boolean(
    existingWithToken && (!existingWithToken.refreshToken || !existingWithToken.tokenExpiresAt)
  );
  const isTokenExpired = Boolean(
    existingWithToken?.tokenExpiresAt && new Date(existingWithToken.tokenExpiresAt).getTime() <= Date.now()
  );

  if (
    sessionToken &&
    (!existingWithToken ||
      existingWithToken.uninstalledAt ||
      !validAccessToken ||
      validAccessToken === "shpss_sample_token" ||
      isLegacyNonExpiring ||
      isTokenExpired)
  ) {
    const exchanged = await exchangeSessionTokenForAccessToken(normalizedShop, sessionToken);
    if (exchanged) {
      validAccessToken = exchanged;
    }
  }

  // If still no valid token or token is expired/expiring soon, refresh via TokenManager
  if (!validAccessToken || validAccessToken === "shpss_sample_token" || isTokenExpired) {
    try {
      const { getValidAdminAccessToken } = await import("./services/shop.server");
      const refreshed = await getValidAdminAccessToken(normalizedShop);
      if (refreshed && refreshed !== "shpss_sample_token") {
        validAccessToken = refreshed;
      }
    } catch {}
  }

  const shopRecord = await registerShop({
    shopifyDomain: normalizedShop,
    accessToken: validAccessToken || "shpss_sample_token",
    scopes:
      process.env.SCOPES ||
      "read_products,read_content,read_customers,read_orders,read_discounts",
  });

  const tenant = await getTenantByDomain(normalizedShop);

  return {
    admin: {
      graphql: async (query: string, options?: { variables?: Record<string, any> }) => {
        const { getValidAdminAccessToken } = await import("./services/shop.server");
        let activeToken = validAccessToken || (await getValidAdminAccessToken(normalizedShop));
        const endpoint = `https://${normalizedShop}/admin/api/2025-01/graphql.json`;

        if (activeToken && activeToken !== "shpss_sample_token") {
          let res = await fetch(endpoint, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-Shopify-Access-Token": activeToken,
            },
            body: JSON.stringify({ query, variables: options?.variables }),
          });

          // If 401 Unauthorized, proactively force-refresh token and retry once
          if (res.status === 401) {
            activeToken = await getValidAdminAccessToken(normalizedShop, true);
            if (activeToken && activeToken !== "shpss_sample_token") {
              res = await fetch(endpoint, {
                method: "POST",
                headers: {
                  "Content-Type": "application/json",
                  "X-Shopify-Access-Token": activeToken,
                },
                body: JSON.stringify({ query, variables: options?.variables }),
              });
            }
          }
          return res;
        }
        return new Response(JSON.stringify({ data: {} }));
      },
    },
    session: {
      shop: normalizedShop,
      shopId: tenant.shopId,
      accessToken: validAccessToken || shopRecord.accessToken,
      sessionTokenVerified: Boolean(verifiedPayload?.signatureVerified),
    },
  };
}

export const authenticate = {
  admin: authenticateAdmin,
};

export async function login(request: Request) {
  const url = new URL(request.url);
  const shop = url.searchParams.get("shop") || "merchant.myshopify.com";
  const appUrl = url.origin;
  return Response.redirect(`${appUrl}/app?shop=${shop}`);
}
