import { json, type ActionFunctionArgs } from "@remix-run/server-runtime";
import { timingSafeEqual } from "crypto";
import { uiComponent } from "@team-plain/typescript-sdk";
import { z } from "zod";
import { prisma } from "~/db.server";
import { env } from "~/env.server";
import { logger } from "~/services/logger.server";
import { generateImpersonationToken } from "~/services/impersonation.server";

// Schema for the request body from Plain
const PlainCustomerCardRequestSchema = z.object({
  cardKeys: z.array(z.string()),
  customer: z
    .object({
      id: z.string(),
      email: z.string().optional(),
      externalId: z.string().optional(),
    })
    .refine((data) => data.email || data.externalId, {
      message: "Either customer.email or customer.externalId must be provided",
      path: ["customer"],
    }),
  thread: z
    .object({
      id: z.string(),
    })
    .optional(),
});

function sanitizeHeaders(
  request: Request,
  skipHeaders?: string[]
): Partial<Record<string, string>> {
  const authHeaderName = (env.PLAIN_CUSTOMER_CARDS_HEADERS || "Authorization").toLowerCase();
  const defaultSkipHeaders = skipHeaders || [authHeaderName, "cookie"];
  const sanitizedHeaders: Partial<Record<string, string>> = {};

  for (const [key, value] of request.headers.entries()) {
    if (!defaultSkipHeaders.includes(key.toLowerCase())) {
      sanitizedHeaders[key] = value;
    }
  }

  return sanitizedHeaders;
}

// Authenticate the request from Plain
function authenticatePlainRequest(request: Request): boolean {
  const authHeaderName = env.PLAIN_CUSTOMER_CARDS_HEADERS || "Authorization";
  const authHeader = request.headers.get(authHeaderName);
  const expectedSecret = env.PLAIN_CUSTOMER_CARDS_SECRET;
  if (!expectedSecret) {
    logger.warn("PLAIN_CUSTOMER_CARDS_SECRET not configured");
    return false;
  }

  if (!authHeader) {
    return false;
  }

  // Support both "Bearer <token>" and plain token formats
  const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;

  // Use constant-time comparison to prevent timing attacks
  const encoder = new TextEncoder();
  const tokenBuffer = encoder.encode(token);
  const secretBuffer = encoder.encode(expectedSecret);
  if (tokenBuffer.byteLength !== secretBuffer.byteLength) {
    return false;
  }
  return timingSafeEqual(tokenBuffer, secretBuffer);
}

export async function action({ request }: ActionFunctionArgs) {
  // Only accept POST requests
  if (request.method !== "POST") {
    return json({ error: "Method not allowed" }, { status: 405 });
  }

  // Authenticate the request
  if (!authenticatePlainRequest(request)) {
    logger.warn("Unauthorized Plain customer card request", {
      headers: sanitizeHeaders(request),
    });
    return json({ error: "Unauthorized" }, { status: 401 });
  }

  // Parse JSON with separate error handling for malformed JSON
  let body: unknown;
  try {
    body = await request.json();
  } catch (error) {
    // Handle JSON parsing errors as client errors (400) instead of server errors (500)
    if (error instanceof SyntaxError || error instanceof TypeError) {
      logger.warn("Malformed JSON in Plain customer card request", {
        error: error.message,
      });
      return json({ error: "Invalid JSON in request body" }, { status: 400 });
    }
    // Re-throw unexpected errors to be caught by outer catch
    throw error;
  }

  // Validate the request body schema
  const parsed = PlainCustomerCardRequestSchema.safeParse(body);

  if (!parsed.success) {
    logger.warn("Invalid Plain customer card request", {
      errors: parsed.error.errors,
    });
    return json({ error: "Invalid request body" }, { status: 400 });
  }

  try {
    const { customer, cardKeys } = parsed.data;

    const userInclude = {
      orgMemberships: {
        where: {
          organization: { deletedAt: null },
        },
        include: {
          organization: {
            include: {
              projects: {
                where: { deletedAt: null },
                take: 10,
                orderBy: { createdAt: "desc" as const },
              },
            },
          },
        },
      },
    };

    const where = customer.externalId
      ? { id: customer.externalId }
      : customer.email
        ? { email: customer.email }
        : null;

    const user = where ? await prisma.user.findFirst({ where, include: userInclude }) : null;

    // If user not found, return empty cards
    if (!user) {
      logger.info("User not found for Plain customer card request", {
        customerId: customer.id,
        externalId: customer.externalId,
        hasEmail: !!customer.email,
      });
      return json({ cards: [] });
    }

    // Build cards based on requested cardKeys
    const cards = [];

    const accountDetailsKey = env.PLAIN_CUSTOMER_CARDS_KEY || "account-details";
    for (const cardKey of cardKeys) {
      switch (cardKey) {
        case accountDetailsKey: {
          // Generate a signed one-time token for impersonation
          const impersonationToken = await generateImpersonationToken(user.id);
          // Build the impersonate URL with token for CSRF protection
          const impersonateUrl = `${env.APP_ORIGIN}/admin/impersonate?impersonate=${user.id}&impersonationToken=${encodeURIComponent(impersonationToken)}`;

          cards.push({
            key: accountDetailsKey,
            timeToLiveSeconds: 15,
            components: [
              uiComponent.container({
                content: [
                  uiComponent.text({
                    text: "Account Details",
                    size: "L",
                    color: "NORMAL",
                  }),
                  uiComponent.spacer({ size: "M" }),
                  uiComponent.row({
                    mainContent: [
                      uiComponent.text({
                        text: "User ID",
                        size: "S",
                        color: "MUTED",
                      }),
                    ],
                    asideContent: [
                      uiComponent.copyButton({
                        value: user.id,
                        tooltip: "Copy",
                      }),
                    ],
                  }),
                  uiComponent.spacer({ size: "S" }),
                  uiComponent.row({
                    mainContent: [
                      uiComponent.text({
                        text: "Email",
                        size: "S",
                        color: "MUTED",
                      }),
                    ],
                    asideContent: [
                      uiComponent.text({
                        text: user.email,
                        size: "S",
                        color: "NORMAL",
                      }),
                    ],
                  }),
                  uiComponent.spacer({ size: "S" }),
                  uiComponent.row({
                    mainContent: [
                      uiComponent.text({
                        text: "Name",
                        size: "S",
                        color: "MUTED",
                      }),
                    ],
                    asideContent: [
                      uiComponent.text({
                        text: user.name || user.displayName || "N/A",
                        size: "S",
                        color: "NORMAL",
                      }),
                    ],
                  }),
                  uiComponent.spacer({ size: "S" }),
                  uiComponent.row({
                    mainContent: [
                      uiComponent.text({
                        text: "Member Since",
                        size: "S",
                        color: "MUTED",
                      }),
                    ],
                    asideContent: [
                      uiComponent.text({
                        text: new Date(user.createdAt).toLocaleDateString(),
                        size: "S",
                        color: "NORMAL",
                      }),
                    ],
                  }),
                  uiComponent.spacer({ size: "M" }),
                  uiComponent.divider({ spacingSize: "M" }),
                  uiComponent.spacer({ size: "M" }),
                  uiComponent.linkButton({
                    label: "Impersonate User",
                    url: impersonateUrl,
                  }),
                ],
              }),
            ],
          });
          break;
        }

        case "organizations": {
          if (user.orgMemberships.length === 0) {
            cards.push({
              key: "organizations",
              timeToLiveSeconds: 300,
              components: [
                uiComponent.container({
                  content: [
                    uiComponent.text({
                      text: "Organizations",
                      size: "L",
                      color: "NORMAL",
                    }),
                    uiComponent.spacer({ size: "M" }),
                    uiComponent.text({
                      text: "No organizations found",
                      size: "S",
                      color: "MUTED",
                    }),
                  ],
                }),
              ],
            });
            break;
          }

          const orgComponents = user.orgMemberships.flatMap(
            (membership: (typeof user.orgMemberships)[0], index: number) => {
              const org = membership.organization;
              const projectCount = org.projects.length;

              return [
                ...(index > 0 ? [uiComponent.divider({ spacingSize: "M" })] : []),
                uiComponent.text({
                  text: org.title,
                  size: "M",
                  color: "NORMAL",
                }),
                uiComponent.spacer({ size: "XS" }),
                uiComponent.row({
                  mainContent: [
                    uiComponent.badge({
                      label: membership.role,
                      color: membership.role === "ADMIN" ? "BLUE" : "GREY",
                    }),
                  ],
                  asideContent: [
                    uiComponent.text({
                      text: `${projectCount} recent project${projectCount !== 1 ? "s" : ""}`,
                      size: "S",
                      color: "MUTED",
                    }),
                  ],
                }),
                uiComponent.spacer({ size: "XS" }),
                uiComponent.linkButton({
                  label: "View in Dashboard",
                  url: `${env.APP_ORIGIN}/@/orgs/${org.slug}`,
                }),
              ];
            }
          );

          cards.push({
            key: "organizations",
            timeToLiveSeconds: 300,
            components: [
              uiComponent.container({
                content: [
                  uiComponent.text({
                    text: "Organizations",
                    size: "L",
                    color: "NORMAL",
                  }),
                  uiComponent.spacer({ size: "M" }),
                  ...orgComponents,
                ],
              }),
            ],
          });
          break;
        }

        case "projects": {
          const allProjects = user.orgMemberships.flatMap((membership) =>
            membership.organization.projects.map((project) => ({
              ...project,
              orgSlug: membership.organization.slug,
            }))
          );

          if (allProjects.length === 0) {
            cards.push({
              key: "projects",
              timeToLiveSeconds: 300,
              components: [
                uiComponent.container({
                  content: [
                    uiComponent.text({
                      text: "Projects",
                      size: "L",
                      color: "NORMAL",
                    }),
                    uiComponent.spacer({ size: "M" }),
                    uiComponent.text({
                      text: "No projects found",
                      size: "S",
                      color: "MUTED",
                    }),
                  ],
                }),
              ],
            });
            break;
          }

          const projectComponents = allProjects
            .slice(0, 10)
            .flatMap((project: (typeof allProjects)[0] & { orgSlug: string }, index: number) => {
              return [
                ...(index > 0 ? [uiComponent.divider({ spacingSize: "M" })] : []),
                uiComponent.text({
                  text: project.name,
                  size: "M",
                  color: "NORMAL",
                }),
                uiComponent.spacer({ size: "XS" }),
                uiComponent.row({
                  mainContent: [
                    uiComponent.badge({
                      label: project.version,
                      color: project.version === "V3" ? "GREEN" : "GREY",
                    }),
                  ],
                  asideContent: [
                    uiComponent.linkButton({
                      label: "View",
                      url: `${env.APP_ORIGIN}/@/orgs/${project.orgSlug}/projects/${project.slug}`,
                    }),
                  ],
                }),
              ];
            });

          cards.push({
            key: "projects",
            timeToLiveSeconds: 300,
            components: [
              uiComponent.container({
                content: [
                  uiComponent.text({
                    text: "Projects",
                    size: "L",
                    color: "NORMAL",
                  }),
                  uiComponent.spacer({ size: "M" }),
                  ...projectComponents,
                ],
              }),
            ],
          });
          break;
        }

        default:
          // Unknown card key - skip it
          logger.info("Unknown card key requested", { cardKey });
          break;
      }
    }

    return json({ cards });
  } catch (error) {
    logger.error("Error processing Plain customer card request", {
      error: error instanceof Error ? error.message : String(error),
      stack: error instanceof Error ? error.stack : undefined,
    });
    return json({ error: "Internal server error" }, { status: 500 });
  }
}
