"use client";

import {
  E2eTestId,
  IDENTITY_PROVIDER_ID,
  IDENTITY_TRUSTED_PROVIDER_IDS,
  type IdentityProviderId,
} from "@shared";
import { useCallback, useState } from "react";
import { EnterpriseLicenseRequired } from "@/components/enterprise-license-required";
import { IdentityProviderIcon } from "@/components/identity-provider-icons.ee";
import { LoadingSpinner } from "@/components/loading";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { useIdentityProviders } from "@/lib/auth/identity-provider.query.ee";
import config from "@/lib/config/config";
import { CreateIdentityProviderDialog } from "./create-identity-provider-dialog.ee";
import { EditIdentityProviderDialog } from "./edit-identity-provider-dialog.ee";

/** Configuration for a predefined identity provider card */
interface IdpConfig {
  /** Internal ID for the config (used as React key) */
  id: string;
  /** Canonical provider ID used for registration and callbacks */
  providerId: string;
  /** Display name */
  name: string;
  /** Short description */
  description: string;
  /** Tailwind background color class for the icon container */
  bgColor: string;
  /** Hide the provider ID field in the form (for predefined providers) */
  hideProviderId: boolean;
  /** Disable PKCE (for providers that don't support it like GitHub) */
  disablePkce?: boolean;
  /** Provider type: oidc or saml */
  providerType: "oidc" | "saml";
  /** Default OIDC configuration values (for OIDC providers) */
  defaultOidcConfig?: {
    issuer: string;
    discoveryEndpoint: string;
    authorizationEndpoint?: string;
    tokenEndpoint?: string;
    userInfoEndpoint?: string;
    jwksEndpoint?: string;
    enableRpInitiatedLogout?: boolean;
    scopes: string[];
    mapping: {
      id: string;
      email: string;
      name: string;
    };
  };
  /** Default SAML configuration values (for SAML providers) */
  defaultSamlConfig?: {
    issuer: string;
    entryPoint: string;
    cert: string;
    callbackUrl: string;
    mapping?: {
      id?: string;
      email?: string;
      name?: string;
      firstName?: string;
      lastName?: string;
    };
  };
}

// Predefined identity provider configurations
const IDP_CONFIGS: IdpConfig[] = [
  {
    id: "okta",
    // Use the canonical provider ID from shared constants
    providerId: IDENTITY_PROVIDER_ID.OKTA,
    name: "Okta",
    description: "Enterprise identity and access management",
    bgColor: "bg-blue-50",
    // Hide the provider ID field for predefined providers
    hideProviderId: true,
    providerType: "oidc",
    defaultOidcConfig: {
      issuer: "",
      discoveryEndpoint: "",
      scopes: ["openid", "email", "profile"],
      mapping: {
        id: "sub",
        email: "email",
        name: "name",
      },
    },
  },
  {
    id: "google",
    providerId: IDENTITY_PROVIDER_ID.GOOGLE,
    name: "Google",
    description: "Sign in with Google OAuth",
    bgColor: "bg-red-50",
    hideProviderId: true,
    providerType: "oidc",
    defaultOidcConfig: {
      issuer: "https://accounts.google.com",
      discoveryEndpoint:
        "https://accounts.google.com/.well-known/openid-configuration",
      authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
      tokenEndpoint: "https://oauth2.googleapis.com/token",
      userInfoEndpoint: "https://openidconnect.googleapis.com/v1/userinfo",
      jwksEndpoint: "https://www.googleapis.com/oauth2/v3/certs",
      scopes: ["openid", "email", "profile"],
      mapping: {
        id: "sub",
        email: "email",
        name: "name",
      },
    },
  },
  {
    /**
     * GitHub OAuth limitation: Users must have a public email set in their
     * GitHub profile settings for SSO to work. This is because the SSO plugin
     * only calls /user endpoint, not /user/emails.
     * See: https://grafana.com/docs/grafana/latest/setup-grafana/configure-access/configure-authentication/github/
     */
    id: "github",
    providerId: IDENTITY_PROVIDER_ID.GITHUB,
    name: "GitHub",
    description: "Sign in with GitHub OAuth (requires public email)",
    bgColor: "bg-gray-50",
    hideProviderId: true,
    providerType: "oidc",
    /**
     * GitHub doesn't support PKCE
     * https://github.com/orgs/community/discussions/15752
     */
    disablePkce: true,
    defaultOidcConfig: {
      issuer: "https://github.com",
      /**
       * GitHub OAuth doesn't have a standard OIDC discovery endpoint
       * https://stackoverflow.com/a/52164558
       * https://docs.github.com/en/actions/concepts/security/openid-connect
       */
      discoveryEndpoint: "",
      authorizationEndpoint: "https://github.com/login/oauth/authorize",
      tokenEndpoint: "https://github.com/login/oauth/access_token",
      userInfoEndpoint: "https://api.github.com/user",
      scopes: ["read:user", "user:email"],
      mapping: {
        id: "id",
        email: "email",
        name: "name",
      },
    },
  },
  {
    id: "gitlab",
    providerId: IDENTITY_PROVIDER_ID.GITLAB,
    name: "GitLab",
    description: "Sign in with GitLab OAuth",
    bgColor: "bg-orange-50",
    hideProviderId: true,
    providerType: "oidc",
    defaultOidcConfig: {
      issuer: "https://gitlab.com",
      discoveryEndpoint: "https://gitlab.com/.well-known/openid-configuration",
      authorizationEndpoint: "https://gitlab.com/oauth/authorize",
      tokenEndpoint: "https://gitlab.com/oauth/token",
      userInfoEndpoint: "https://gitlab.com/oauth/userinfo",
      jwksEndpoint: "https://gitlab.com/oauth/discovery/keys",
      scopes: ["openid", "email", "profile"],
      mapping: {
        id: "sub",
        email: "email",
        name: "name",
      },
    },
  },
  {
    id: "entra",
    providerId: IDENTITY_PROVIDER_ID.ENTRA_ID,
    name: "Microsoft Entra ID",
    description: "Sign in with Microsoft (Azure AD)",
    bgColor: "bg-sky-50",
    hideProviderId: true,
    providerType: "oidc",
    /**
     * Microsoft Entra ID (formerly Azure AD) configuration
     * Users need to replace {tenant-id} with their Azure tenant ID
     * See: https://grafana.com/docs/grafana/latest/setup-grafana/configure-access/configure-authentication/entraid/
     */
    defaultOidcConfig: {
      issuer: "https://login.microsoftonline.com/{tenant-id}/v2.0",
      discoveryEndpoint:
        "https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration",
      authorizationEndpoint:
        "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize",
      tokenEndpoint:
        "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token",
      userInfoEndpoint: "https://graph.microsoft.com/oidc/userinfo",
      jwksEndpoint:
        "https://login.microsoftonline.com/{tenant-id}/discovery/v2.0/keys",
      scopes: ["openid", "email", "profile"],
      mapping: {
        id: "sub",
        email: "email",
        name: "name",
      },
    },
  },
  {
    id: "generic-oidc",
    // Generic OAuth allows custom provider IDs
    providerId: "",
    name: "Generic OIDC",
    description: "Configure any OpenID Connect provider",
    bgColor: "bg-purple-50",
    // Show the provider ID field for generic providers
    hideProviderId: false,
    providerType: "oidc",
    defaultOidcConfig: {
      issuer: "",
      discoveryEndpoint: "",
      scopes: ["openid", "email", "profile"],
      mapping: {
        id: "sub",
        email: "email",
        name: "name",
      },
    },
  },
  {
    id: "generic-saml",
    // Generic SAML allows custom provider IDs
    providerId: "",
    name: "Generic SAML",
    description: "Configure any SAML 2.0 identity provider",
    bgColor: "bg-indigo-50",
    // Show the provider ID field for generic providers
    hideProviderId: false,
    providerType: "saml",
    defaultSamlConfig: {
      issuer: "",
      entryPoint: "",
      cert: "",
      callbackUrl: "",
      mapping: {
        id: "nameID",
        email: "email",
        name: "name",
        firstName: "firstName",
        lastName: "lastName",
      },
    },
  },
];

type IdentityProvider = NonNullable<
  ReturnType<typeof useIdentityProviders>["data"]
>[number];

export function IdentityProvidersSettingsContent() {
  const { data: identityProviders = [], isLoading } = useIdentityProviders();
  const [createConfig, setCreateConfig] = useState<{
    providerId: string;
    config: IdpConfig;
  } | null>(null);
  const [editingProvider, setEditingProvider] =
    useState<IdentityProvider | null>(null);

  // Find existing providers by matching provider ID
  const getProviderStatus = useCallback(
    (config: IdpConfig) => {
      const provider = identityProviders.find((p) => {
        // For predefined providers, match exactly by canonical provider ID
        if (config.providerId) {
          return p.providerId === config.providerId;
        }

        // For generic providers (empty providerId), match by provider type as well
        // Check if this is a non-trusted provider and matches the same type (OIDC vs SAML)
        const isNonTrustedProvider = !IDENTITY_TRUSTED_PROVIDER_IDS.includes(
          p.providerId as IdentityProviderId,
        );
        if (!isNonTrustedProvider) {
          return false;
        }

        // Determine provider type from config presence
        const existingProviderType = p.samlConfig ? "saml" : "oidc";
        return existingProviderType === config.providerType;
      });
      return provider;
    },
    [identityProviders],
  );

  const handleProviderClick = useCallback(
    (config: IdpConfig) => {
      const existingProvider = getProviderStatus(config);

      if (existingProvider) {
        // Edit existing provider
        setEditingProvider(existingProvider);
      } else {
        // Create new provider
        setCreateConfig({
          providerId: config.id,
          config,
        });
      }
    },
    [getProviderStatus],
  );

  // Show message if SSO feature is disabled (check before loading since query is disabled)
  if (!config.enterpriseFeatures.core) {
    return <EnterpriseLicenseRequired featureName="Identity Providers" />;
  }

  if (isLoading) return <LoadingSpinner />;

  return (
    <div>
      <div className="grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4">
        {IDP_CONFIGS.map((config) => {
          const existingProvider = getProviderStatus(config);

          return (
            <Card
              key={config.id}
              className="cursor-pointer hover:shadow-md transition-shadow flex flex-col h-full"
              onClick={() => handleProviderClick(config)}
              data-testid={`${E2eTestId.IdentityProviderCard}-${config.id}`}
            >
              <CardHeader className="pb-3">
                <div className="flex items-center justify-between">
                  <div
                    className={`p-2 rounded-lg ${config.bgColor} text-gray-900`}
                  >
                    <IdentityProviderIcon
                      providerId={config.providerId || config.id}
                      size={24}
                    />
                  </div>
                  <Badge variant={existingProvider ? "default" : "secondary"}>
                    {existingProvider ? "Enabled" : "Not enabled"}
                  </Badge>
                </div>
                <CardTitle className="text-lg">{config.name}</CardTitle>
              </CardHeader>
              <CardContent className="flex flex-col flex-1">
                <div className="flex-1 min-h-[2.5rem] flex flex-col justify-end mb-4">
                  <p className="text-sm text-muted-foreground">
                    {config.description}
                  </p>
                </div>
                <Button
                  variant={existingProvider ? "outline" : "default"}
                  size="sm"
                  className="w-full"
                  data-testid={`${E2eTestId.IdentityProviderOpenDialogButton}-${config.id}`}
                >
                  {existingProvider ? "Edit" : "Enable"}
                </Button>
              </CardContent>
            </Card>
          );
        })}
      </div>

      {/* Create Dialog */}
      {createConfig && (
        <CreateIdentityProviderDialog
          open={!!createConfig}
          onOpenChange={(open) => !open && setCreateConfig(null)}
          defaultValues={
            createConfig.config.providerType === "saml"
              ? {
                  providerId: createConfig.config.providerId || "",
                  issuer: createConfig.config.defaultSamlConfig?.issuer || "",
                  domain: "",
                  providerType: "saml" as const,
                  roleMapping: { rules: [] },
                  samlConfig: {
                    issuer: createConfig.config.defaultSamlConfig?.issuer || "",
                    entryPoint:
                      createConfig.config.defaultSamlConfig?.entryPoint || "",
                    cert: createConfig.config.defaultSamlConfig?.cert || "",
                    callbackUrl:
                      createConfig.config.defaultSamlConfig?.callbackUrl ||
                      `${typeof window !== "undefined" ? window.location.origin : ""}/api/auth/sso/saml2/sp/acs/{providerId}`,
                    spMetadata: {},
                    mapping:
                      createConfig.config.defaultSamlConfig?.mapping || {},
                  },
                }
              : {
                  providerId: createConfig.config.providerId || "",
                  issuer: createConfig.config.defaultOidcConfig?.issuer || "",
                  domain: "",
                  providerType: "oidc" as const,
                  roleMapping: { rules: [] },
                  oidcConfig: {
                    ...createConfig.config.defaultOidcConfig,
                    issuer: createConfig.config.defaultOidcConfig?.issuer || "",
                    discoveryEndpoint:
                      createConfig.config.defaultOidcConfig
                        ?.discoveryEndpoint || "",
                    enableRpInitiatedLogout:
                      createConfig.config.defaultOidcConfig
                        ?.enableRpInitiatedLogout ?? true,
                    scopes: createConfig.config.defaultOidcConfig?.scopes || [
                      "openid",
                      "email",
                      "profile",
                    ],
                    mapping: createConfig.config.defaultOidcConfig?.mapping || {
                      id: "sub",
                      email: "email",
                      name: "name",
                    },
                    clientId: "",
                    clientSecret: "",
                    pkce: !createConfig.config.disablePkce,
                    overrideUserInfo: true,
                  },
                }
          }
          providerName={createConfig.config.name}
          hidePkce={createConfig.config.disablePkce}
          hideProviderId={createConfig.config.hideProviderId}
          providerType={createConfig.config.providerType}
        />
      )}

      {/* Edit Dialog */}
      {editingProvider && (
        <EditIdentityProviderDialog
          identityProviderId={editingProvider.id}
          open={!!editingProvider}
          onOpenChange={(open) => !open && setEditingProvider(null)}
        />
      )}
    </div>
  );
}
