import crypto from "crypto";
import { ShopifyCustomerAccountClient, CustomerSession } from "../lib/shopify/customer-account";
import { ShopifyStorefrontClient } from "../lib/shopify/storefront";
import { ShopifyAdminClient } from "../lib/shopify/admin";
import { getTenantByDomain } from "../lib/security/tenant-context";
import { ValidationError, UnauthorizedError } from "../lib/security/errors";
import prisma from "../db.server";

const CUSTOMER_SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 days

function getSessionSigningKey(): Buffer {
  const secret = process.env.ENCRYPTION_KEY || process.env.SAAS_ADMIN_KEY || "default_customer_session_signing_secret_32bytes!";
  return crypto.createHash("sha256").update(secret).digest();
}

/**
 * Creates a cryptographically signed HMAC-SHA256 customer session token.
 * Format: ccs.v1.<shopId>.<customerId>.<expiresAtTimestamp>.<hmacSignatureHex>
 */
export function createSignedCustomerSessionToken(
  shopId: string,
  customerId: string,
  ttlMs: number = CUSTOMER_SESSION_TTL_MS
): string {
  const expiresAt = Date.now() + ttlMs;
  const key = getSessionSigningKey();
  const hmac = crypto.createHmac("sha256", key);
  hmac.update(`customer_session:${shopId}:${customerId}:${expiresAt}`);
  const signature = hmac.digest("hex");
  return `ccs.v1.${shopId}.${customerId}.${expiresAt}.${signature}`;
}

/**
 * Verifies a signed HMAC-SHA256 customer session token in constant time.
 * Enforces shopId tenant boundary and expiration.
 */
export function verifySignedCustomerSessionToken(
  token: string,
  expectedShopId: string
): { customerId: string; shopId: string; expiresAt: number } {
  if (!token || !token.startsWith("ccs.v1.")) {
    throw new UnauthorizedError("Invalid or malformed customer session token format.");
  }

  const parts = token.split(".");
  if (parts.length !== 6) {
    throw new UnauthorizedError("Malformed customer session token structure.");
  }

  const [, , tokenShopId, customerId, expiresAtStr, signatureHex] = parts;
  const expiresAt = parseInt(expiresAtStr, 10);

  if (isNaN(expiresAt) || Date.now() > expiresAt) {
    throw new UnauthorizedError("Customer session token has expired. Please log in again.");
  }

  if (tokenShopId !== expectedShopId) {
    throw new UnauthorizedError("Cross-tenant customer session token rejection.");
  }

  const key = getSessionSigningKey();
  const hmac = crypto.createHmac("sha256", key);
  hmac.update(`customer_session:${tokenShopId}:${customerId}:${expiresAtStr}`);
  const expectedSignature = hmac.digest("hex");

  const sigBuf = Buffer.from(signatureHex, "hex");
  const expectedBuf = Buffer.from(expectedSignature, "hex");

  if (sigBuf.length !== expectedBuf.length || !crypto.timingSafeEqual(sigBuf, expectedBuf)) {
    throw new UnauthorizedError("Tampered or invalid customer session token signature.");
  }

  return { customerId, shopId: tokenShopId, expiresAt };
}

export interface CustomerAuthResult {
  success: boolean;
  customerAccessToken?: string;
  expiresAt?: string;
  customer?: {
    id: string;
    shopifyCustomerId?: string;
    email: string;
    firstName?: string;
    lastName?: string;
    phone?: string;
    orders?: any[];
  };
  message?: string;
  error?: string;
}

/**
 * Authenticates customer credentials strictly against Shopify.
 * ZERO TOLERANCE: Never falls back to passwordless database login.
 */
export async function loginCustomer(
  shopDomain: string,
  emailOrData: string | { email?: string; password?: string },
  passwordArg?: string
): Promise<CustomerAuthResult> {
  const email = typeof emailOrData === "object" ? (emailOrData.email || "") : (emailOrData || "");
  const password = typeof emailOrData === "object" ? (emailOrData.password || "") : (passwordArg || "");

  if (!email || !password) {
    throw new ValidationError("Email and password are required.");
  }

  const cleanEmail = email.trim().toLowerCase();
  const tenant = await getTenantByDomain(shopDomain);
  const shopId = tenant.shopId;

  // 1. Authenticate against Shopify Storefront API
  try {
    const client = new ShopifyStorefrontClient({ shopifyDomain: shopDomain });
    const res = await client.customerAccessTokenCreate({ email: cleanEmail, password });
    const payload = res?.customerAccessTokenCreate;

    if (payload?.customerUserErrors && payload.customerUserErrors.length > 0) {
      const firstErr = payload.customerUserErrors[0];
      const message = firstErr.code === "UNIDENTIFIED_CUSTOMER"
        ? "Incorrect email or password. Please check your credentials."
        : (firstErr.message || "Invalid customer credentials.");
      return {
        success: false,
        error: message,
      };
    }

    if (payload?.customerAccessToken?.accessToken) {
      const sfToken = payload.customerAccessToken.accessToken;
      const profile = await getCustomerProfile(shopDomain, sfToken);

      if (profile.customer?.shopifyCustomerId) {
        const savedCustomer = await prisma.customer.upsert({
          where: {
            shopId_shopifyCustomerId: {
              shopId,
              shopifyCustomerId: profile.customer.shopifyCustomerId,
            },
          },
          update: {
            email: cleanEmail,
            firstName: profile.customer.firstName,
            lastName: profile.customer.lastName,
            phone: profile.customer.phone,
          },
          create: {
            shopId,
            shopifyCustomerId: profile.customer.shopifyCustomerId,
            email: cleanEmail,
            firstName: profile.customer.firstName,
            lastName: profile.customer.lastName,
            phone: profile.customer.phone,
          },
        });

        // Issue cryptographically signed session token bound to tenant and customer
        const signedSessionToken = createSignedCustomerSessionToken(shopId, savedCustomer.id);

        return {
          success: true,
          customerAccessToken: signedSessionToken,
          expiresAt: new Date(Date.now() + CUSTOMER_SESSION_TTL_MS).toISOString(),
          customer: profile.customer,
          message: "Successfully logged in to your store account!",
        };
      }
    }
  } catch (e: any) {
    // Fail closed if Shopify Storefront API is unreachable or errors out
    return {
      success: false,
      error: e.message || "Customer authentication service is temporarily unavailable. Please try again later.",
    };
  }

  // Strictly reject login when credentials could not be verified
  return {
    success: false,
    error: "Incorrect email or password. Please check your credentials.",
  };
}

/**
 * Registers a new customer account with Shopify.
 */
export async function registerCustomer(
  shopDomain: string,
  data: {
    firstName?: string;
    lastName?: string;
    email: string;
    password: string;
  }
): Promise<CustomerAuthResult> {
  const { firstName = "", lastName = "", email, password } = data;

  if (!email || !email.includes("@")) {
    throw new ValidationError("A valid email address is required.");
  }
  if (!password || password.length < 5) {
    throw new ValidationError("Password must be at least 5 characters long.");
  }

  const cleanEmail = email.trim().toLowerCase();
  const cleanFirst = firstName.trim();
  const cleanLast = lastName.trim();
  const tenant = await getTenantByDomain(shopDomain);
  const shopId = tenant.shopId;

  // Check if customer already exists locally
  const alreadyExists = await prisma.customer.findFirst({
    where: { shopId, email: cleanEmail },
  });
  if (alreadyExists) {
    return {
      success: false,
      error: "An account with this email address already exists. Please sign in.",
    };
  }

  // 1. Register with Shopify Storefront API
  try {
    const client = new ShopifyStorefrontClient({ shopifyDomain: shopDomain });
    const res = await client.customerCreate({
      firstName: cleanFirst,
      lastName: cleanLast,
      email: cleanEmail,
      password,
    });
    const payload = res?.customerCreate;

    if (payload?.customerUserErrors && payload.customerUserErrors.length > 0) {
      const err = payload.customerUserErrors[0];
      return {
        success: false,
        error: err.message || "Could not create account in Shopify.",
      };
    }

    if (payload?.customer?.id) {
      const created = await prisma.customer.upsert({
        where: {
          shopId_shopifyCustomerId: {
            shopId,
            shopifyCustomerId: payload.customer.id,
          },
        },
        update: {
          email: cleanEmail,
          firstName: cleanFirst,
          lastName: cleanLast,
        },
        create: {
          shopId,
          shopifyCustomerId: payload.customer.id,
          email: cleanEmail,
          firstName: cleanFirst,
          lastName: cleanLast,
        },
      });

      const signedToken = createSignedCustomerSessionToken(shopId, created.id);

      return {
        success: true,
        customerAccessToken: signedToken,
        customer: {
          id: created.id,
          shopifyCustomerId: payload.customer.id,
          email: cleanEmail,
          firstName: cleanFirst,
          lastName: cleanLast,
        },
        message: "Customer account created in Shopify! You are now logged in.",
      };
    }
  } catch (e: any) {
    // Fallback to Shopify Admin API if Storefront API customer registration is disabled
  }

  // 2. Register via Shopify Admin API
  try {
    const adminClient = new ShopifyAdminClient({ shopifyDomain: shopDomain });
    const adminRes = await adminClient.createCustomer({
      firstName: cleanFirst,
      lastName: cleanLast,
      email: cleanEmail,
      note: "Customer registered via ConverseCart Chatbot",
    });
    const adminPayload = adminRes?.customerCreate;

    if (adminPayload?.userErrors && adminPayload.userErrors.length > 0) {
      return {
        success: false,
        error: adminPayload.userErrors[0].message || "Could not create account in Shopify.",
      };
    }

    if (adminPayload?.customer?.id) {
      const shopifyCustomerId = adminPayload.customer.id;

      const created = await prisma.customer.upsert({
        where: {
          shopId_shopifyCustomerId: {
            shopId,
            shopifyCustomerId,
          },
        },
        update: {
          email: cleanEmail,
          firstName: cleanFirst,
          lastName: cleanLast,
        },
        create: {
          shopId,
          shopifyCustomerId,
          email: cleanEmail,
          firstName: cleanFirst,
          lastName: cleanLast,
        },
      });

      const signedToken = createSignedCustomerSessionToken(shopId, created.id);

      return {
        success: true,
        customerAccessToken: signedToken,
        customer: {
          id: created.id,
          shopifyCustomerId,
          email: cleanEmail,
          firstName: cleanFirst,
          lastName: cleanLast,
        },
        message: "Customer account created in Shopify! You are now logged in.",
      };
    }
  } catch (adminErr: any) {
    return {
      success: false,
      error: adminErr.message || "Failed to register customer account with Shopify.",
    };
  }

  return {
    success: false,
    error: "Unable to register customer in Shopify. Please verify your details.",
  };
}

export async function recoverCustomerPassword(
  shopDomain: string,
  email: string
): Promise<{ success: boolean; message: string; error?: string }> {
  if (!email || !email.includes("@")) {
    throw new ValidationError("A valid email address is required.");
  }

  const cleanEmail = email.trim().toLowerCase();

  try {
    const client = new ShopifyStorefrontClient({ shopifyDomain: shopDomain });
    const res = await client.customerRecover(cleanEmail);
    const payload = res?.customerRecover;
    if (payload?.customerUserErrors && payload.customerUserErrors.length > 0) {
      return { success: false, error: payload.customerUserErrors[0].message, message: payload.customerUserErrors[0].message };
    }
  } catch (e: any) {
    // Gracefully handle network/storefront recovery call
  }

  return {
    success: true,
    message: `Password reset instructions have been sent to ${cleanEmail}. Please check your inbox.`,
  };
}

export async function logoutCustomer(
  shopDomain: string,
  customerAccessToken?: string
): Promise<{ success: boolean; message: string }> {
  if (customerAccessToken && !customerAccessToken.startsWith("ccs.v1.")) {
    try {
      const client = new ShopifyStorefrontClient({ shopifyDomain: shopDomain });
      await client.customerAccessTokenDelete(customerAccessToken);
    } catch (e) {}
  }

  return {
    success: true,
    message: "Successfully logged out.",
  };
}

/**
 * Retrieves authenticated customer profile using verified cryptographic token.
 * REJECTS predictable or unverified tokens.
 */
export async function getCustomerProfile(
  shopDomain: string,
  customerAccessToken: string
): Promise<CustomerAuthResult> {
  if (!customerAccessToken) {
    throw new UnauthorizedError("Customer access token is required.");
  }

  // Reject deprecated predictable tokens
  if (customerAccessToken.startsWith("shopify_session_")) {
    throw new UnauthorizedError("Deprecated or unauthenticated customer session token format. Please re-authenticate.");
  }

  const tenant = await getTenantByDomain(shopDomain);
  const shopId = tenant.shopId;

  // 1. Verify signed HMAC session token
  if (customerAccessToken.startsWith("ccs.v1.")) {
    const verified = verifySignedCustomerSessionToken(customerAccessToken, shopId);
    const customer = await prisma.customer.findFirst({
      where: {
        id: verified.customerId,
        shopId,
      },
    });

    if (!customer) {
      throw new UnauthorizedError("Customer record not found for this session.");
    }

    return {
      success: true,
      customer: {
        id: customer.id,
        shopifyCustomerId: customer.shopifyCustomerId,
        email: customer.email || "",
        firstName: customer.firstName || "Customer",
        lastName: customer.lastName || "",
        phone: customer.phone || undefined,
      },
    };
  }

  // 2. Verify Shopify Storefront API token
  try {
    const client = new ShopifyStorefrontClient({ shopifyDomain: shopDomain });
    const res = await client.getCustomer(customerAccessToken);
    const customerData = res?.customer;

    if (customerData?.id) {
      return {
        success: true,
        customer: {
          id: customerData.id,
          shopifyCustomerId: customerData.id,
          email: customerData.email,
          firstName: customerData.firstName,
          lastName: customerData.lastName,
          phone: customerData.phone,
          orders: customerData.orders?.edges?.map((e: any) => e.node) || [],
        },
      };
    }
  } catch (e: any) {
    // Fallback to customer account client
  }

  // 3. Verify Customer Account API OAuth token
  try {
    const accountClient = new ShopifyCustomerAccountClient(shopDomain);
    const session = await accountClient.verifyCustomerSessionToken(customerAccessToken);

    return {
      success: true,
      customer: {
        id: session.shopifyCustomerId,
        shopifyCustomerId: session.shopifyCustomerId,
        email: session.email || "",
        firstName: session.firstName,
        lastName: session.lastName,
      },
    };
  } catch (e) {
    throw new UnauthorizedError("Invalid or expired customer session.");
  }
}

/**
 * Verifies customer session for backend AI tool calls and customer-authenticated services.
 * REJECTS predictable or unverified tokens.
 */
export async function verifyAndGetCustomer(
  shopDomain: string,
  customerAccessToken: string
): Promise<CustomerSession> {
  if (!customerAccessToken) {
    throw new UnauthorizedError("Customer access token is required.");
  }

  // Reject deprecated predictable tokens
  if (customerAccessToken.startsWith("shopify_session_")) {
    throw new UnauthorizedError("Deprecated customer session token rejected. Please re-authenticate.");
  }

  const tenant = await getTenantByDomain(shopDomain);
  const shopId = tenant.shopId;

  // 1. Verify signed HMAC session token
  if (customerAccessToken.startsWith("ccs.v1.")) {
    const verified = verifySignedCustomerSessionToken(customerAccessToken, shopId);
    const customer = await prisma.customer.findFirst({
      where: {
        id: verified.customerId,
        shopId,
      },
    });

    if (!customer) {
      throw new UnauthorizedError("Customer record not found for this session.");
    }

    return {
      shopifyCustomerId: customer.shopifyCustomerId,
      email: customer.email || "",
      firstName: customer.firstName || "Customer",
      lastName: customer.lastName || "",
    };
  }

  // 2. Verify Customer Account API OAuth token
  try {
    const client = new ShopifyCustomerAccountClient(shopDomain);
    const customerSession = await client.verifyCustomerSessionToken(customerAccessToken);

    // Sync / update local customer record
    await prisma.customer.upsert({
      where: {
        shopId_shopifyCustomerId: {
          shopId,
          shopifyCustomerId: customerSession.shopifyCustomerId,
        },
      },
      update: {
        email: customerSession.email,
        firstName: customerSession.firstName,
        lastName: customerSession.lastName,
      },
      create: {
        shopId,
        shopifyCustomerId: customerSession.shopifyCustomerId,
        email: customerSession.email,
        firstName: customerSession.firstName,
        lastName: customerSession.lastName,
      },
    });

    return customerSession;
  } catch (e) {
    throw new UnauthorizedError("Customer session verification failed.");
  }
}

export async function generateMultipassToken(
  shopDomain: string,
  customerData: {
    email: string;
    firstName?: string;
    lastName?: string;
    returnTo?: string;
  },
  multipassSecret: string
): Promise<string> {
  if (!multipassSecret) {
    throw new ValidationError("Multipass is not configured for this merchant.");
  }
  if (!customerData.email) {
    throw new ValidationError("Customer email is required for Multipass authentication.");
  }

  // 1. Derive encryption and signature keys from Multipass secret
  const hash = crypto.createHash("sha256").update(multipassSecret).digest();
  const encryptionKey = hash.subarray(0, 16);
  const signatureKey = hash.subarray(16, 32);

  // 2. Prepare payload
  const payload = {
    email: customerData.email.trim().toLowerCase(),
    created_at: new Date().toISOString(),
    first_name: customerData.firstName || undefined,
    last_name: customerData.lastName || undefined,
    return_to: customerData.returnTo || undefined,
  };

  const payloadString = JSON.stringify(payload);

  // 3. Encrypt payload with AES-128-CBC
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv("aes-128-cbc", encryptionKey, iv);
  const encrypted = Buffer.concat([cipher.update(payloadString, "utf8"), cipher.final()]);

  // 4. Compute HMAC-SHA256 signature
  const ciphertext = Buffer.concat([iv, encrypted]);
  const hmac = crypto.createHmac("sha256", signatureKey);
  hmac.update(ciphertext);
  const signature = hmac.digest();

  // 5. Build base64url token
  const tokenBuffer = Buffer.concat([ciphertext, signature]);
  return tokenBuffer
    .toString("base64")
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

export async function detectCustomerAccountType(shopDomain: string): Promise<{
  mode: "classic" | "new" | "multipass";
  loginUrl: string;
  registerUrl: string;
}> {
  const normalizedShop = shopDomain.toLowerCase().trim();

  return {
    mode: "classic",
    loginUrl: `https://${normalizedShop}/account/login`,
    registerUrl: `https://${normalizedShop}/account/register`,
  };
}
