import { useCallback, useState } from "react";
import { useAtomSet } from "@effect/atom-react";
import * as Exit from "effect/Exit";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";

import { useScope } from "@executor-js/react/api/scope-context";
import { sourceWriteKeys } from "@executor-js/react/api/reactivity-keys";
import {
  HttpCredentialsEditor,
  httpCredentialsValid,
  serializeConfigureHttpCredentials,
  serializeHttpCredentials,
  serializeTemplateHttpCredentials,
  type HttpCredentialsState,
} from "@executor-js/react/plugins/http-credentials";
import {
  sourceDisplayNameFromUrl,
  slugifyNamespace,
  useSourceIdentity,
} from "@executor-js/react/plugins/source-identity";
import {
  oauthCallbackUrl,
  oauthConnectionId,
  useOAuthPopupFlow,
  type OAuthCompletionPayload,
} from "@executor-js/react/plugins/oauth-sign-in";
import {
  CredentialControlField,
  CredentialUsageRow,
  useCredentialTargetScope,
} from "@executor-js/react/plugins/credential-target-scope";
import { useSecretPickerSecrets } from "@executor-js/react/plugins/use-secret-picker-secrets";
import { Button } from "@executor-js/react/components/button";
import { FilterTabs } from "@executor-js/react/components/filter-tabs";
import { FloatActions } from "@executor-js/react/components/float-actions";
import { Spinner } from "@executor-js/react/components/spinner";
import { addGraphqlSourceOptimistic } from "./atoms";
import { initialGraphqlCredentials } from "./defaults";
import { GraphqlSourceFields } from "./GraphqlSourceFields";
import type { GraphqlConfiguredValueInput, GraphqlCredentialInput } from "../sdk/types";

const ErrorMessage = Schema.Struct({ message: Schema.String });
const decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);

const errorMessageFromExit = (exit: Exit.Exit<unknown, unknown>, fallback: string): string =>
  Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
    onNone: () => fallback,
    onSome: ({ message }) => message,
  });

type AuthMode = "none" | "oauth2";

export default function AddGraphqlSource(props: {
  onComplete: () => void;
  onCancel: () => void;
  initialUrl?: string;
}) {
  const [endpoint, setEndpoint] = useState(props.initialUrl ?? "");
  const identity = useSourceIdentity({
    fallbackName: sourceDisplayNameFromUrl(endpoint, "GraphQL") ?? "",
  });
  const [credentials, setCredentials] = useState<HttpCredentialsState>(initialGraphqlCredentials);
  const [adding, setAdding] = useState(false);
  const [addError, setAddError] = useState<string | null>(null);
  const [authMode, setAuthMode] = useState<AuthMode>("none");
  const [tokens, setTokens] = useState<OAuthCompletionPayload | null>(null);

  const scopeId = useScope();
  const { credentialTargetScope: requestCredentialTargetScope } = useCredentialTargetScope();
  const {
    credentialTargetScope: oauthCredentialTargetScope,
    setCredentialTargetScope: setOAuthCredentialTargetScope,
    credentialScopeOptions,
  } = useCredentialTargetScope();
  const doAdd = useAtomSet(addGraphqlSourceOptimistic(scopeId), {
    mode: "promiseExit",
  });
  const secretList = useSecretPickerSecrets();
  const oauth = useOAuthPopupFlow({
    popupName: "graphql-oauth",
    startErrorMessage: "Failed to start OAuth",
  });

  const canAdd =
    endpoint.trim().length > 0 &&
    httpCredentialsValid(credentials) &&
    (authMode === "none" || tokens !== null) &&
    !oauth.busy;

  const sourceIdentity = useCallback(() => {
    const trimmedEndpoint = endpoint.trim();
    const namespace =
      slugifyNamespace(identity.namespace) ||
      slugifyNamespace(sourceDisplayNameFromUrl(trimmedEndpoint, "GraphQL") ?? "") ||
      "graphql";
    const displayName =
      identity.name.trim() || sourceDisplayNameFromUrl(trimmedEndpoint, "GraphQL") || namespace;
    return { trimmedEndpoint, namespace, displayName };
  }, [endpoint, identity.name, identity.namespace]);

  const handleOAuth = useCallback(async () => {
    if (!endpoint.trim() || !httpCredentialsValid(credentials)) return;
    setAddError(null);
    const { trimmedEndpoint, namespace, displayName } = sourceIdentity();
    const { headers, queryParams } = serializeHttpCredentials(credentials);
    await oauth.start({
      payload: {
        endpoint: trimmedEndpoint,
        ...(Object.keys(headers).length > 0 ? { headers } : {}),
        ...(Object.keys(queryParams).length > 0 ? { queryParams } : {}),
        redirectUrl: oauthCallbackUrl(),
        connectionId: oauthConnectionId({ pluginId: "graphql", namespace }),
        tokenScope: oauthCredentialTargetScope,
        strategy: { kind: "dynamic-dcr" },
        pluginId: "graphql",
        identityLabel: `${displayName} OAuth`,
      },
      onSuccess: (result) => {
        setTokens({
          connectionId: result.connectionId,
          expiresAt: result.expiresAt,
          scope: result.scope,
        });
      },
      onError: setAddError,
    });
  }, [endpoint, credentials, oauth, sourceIdentity, oauthCredentialTargetScope]);

  const handleAdd = async () => {
    setAdding(true);
    setAddError(null);
    const { headers: templateHeaders, queryParams: templateQueryParams } =
      serializeTemplateHttpCredentials(credentials);
    const { headers: configureHeaders, queryParams: configureQueryParams } =
      serializeConfigureHttpCredentials(credentials, requestCredentialTargetScope);
    const hasInitialCredentials =
      Object.keys(configureHeaders).length > 0 ||
      Object.keys(configureQueryParams).length > 0 ||
      (authMode === "oauth2" && tokens);

    const { trimmedEndpoint, namespace, displayName } = sourceIdentity();
    const exit = await doAdd({
      params: { scopeId },
      payload: {
        endpoint: trimmedEndpoint,
        name: displayName,
        namespace,
        ...(Object.keys(templateHeaders).length > 0
          ? { headers: templateHeaders as Record<string, GraphqlConfiguredValueInput> }
          : {}),
        ...(Object.keys(templateQueryParams).length > 0
          ? {
              queryParams: templateQueryParams as Record<string, GraphqlConfiguredValueInput>,
            }
          : {}),
        ...(hasInitialCredentials
          ? {
              credentials: {
                scope: requestCredentialTargetScope,
                ...(Object.keys(configureHeaders).length > 0
                  ? { headers: configureHeaders as Record<string, GraphqlCredentialInput> }
                  : {}),
                ...(Object.keys(configureQueryParams).length > 0
                  ? {
                      queryParams: configureQueryParams as Record<string, GraphqlCredentialInput>,
                    }
                  : {}),
                ...(authMode === "oauth2" && tokens
                  ? {
                      auth: {
                        oauth2: {
                          connection: {
                            kind: "connection" as const,
                            connectionId: tokens.connectionId,
                          },
                        },
                      },
                    }
                  : {}),
              },
            }
          : {}),
      },
      reactivityKeys: sourceWriteKeys,
    });
    if (Exit.isFailure(exit)) {
      setAddError(errorMessageFromExit(exit, "Failed to add source"));
      setAdding(false);
      return;
    }
    props.onComplete();
  };

  return (
    <div className="flex flex-1 flex-col gap-6">
      <h1 className="text-xl font-semibold text-foreground">Add GraphQL Source</h1>

      <GraphqlSourceFields endpoint={endpoint} onEndpointChange={setEndpoint} identity={identity} />

      <HttpCredentialsEditor
        credentials={credentials}
        onChange={setCredentials}
        existingSecrets={secretList}
        sourceName={identity.name}
        targetScope={requestCredentialTargetScope}
        credentialScopeOptions={credentialScopeOptions}
        bindingScopeOptions={credentialScopeOptions}
      />

      {/* Temporarily hidden while we revisit GraphQL OAuth discovery and UX. */}
      <section className="hidden space-y-2.5">
        <div className="flex items-center justify-between gap-3">
          <span className="text-sm font-medium text-foreground">Authentication</span>
          <FilterTabs<AuthMode>
            tabs={[
              { value: "none", label: "None" },
              { value: "oauth2", label: "OAuth" },
            ]}
            value={authMode}
            onChange={(value) => {
              setAuthMode(value);
              setTokens(null);
            }}
          />
        </div>

        {authMode === "oauth2" && (
          <CredentialUsageRow
            value={oauthCredentialTargetScope}
            options={credentialScopeOptions}
            onChange={(targetScope) => {
              setOAuthCredentialTargetScope(targetScope);
              setTokens(null);
            }}
            label="Connection saved to"
            help="Choose who can use the OAuth connection."
          >
            <CredentialControlField label="Connect via OAuth" help="Start the provider OAuth flow.">
              <div className="flex min-h-9 items-center gap-2 rounded-md border border-border bg-muted/30 px-3 py-2">
                {tokens ? (
                  <span className="text-xs font-medium text-emerald-600 dark:text-emerald-400">
                    Authenticated
                  </span>
                ) : (
                  <span className="text-xs text-muted-foreground">Not connected</span>
                )}
                <Button
                  type="button"
                  variant="outline"
                  size="sm"
                  className="ml-auto h-7 px-2 text-xs"
                  onClick={() => void handleOAuth()}
                  disabled={!endpoint.trim() || !httpCredentialsValid(credentials) || oauth.busy}
                >
                  {oauth.busy ? "Signing in..." : tokens ? "Reconnect" : "Sign in"}
                </Button>
              </div>
            </CredentialControlField>
          </CredentialUsageRow>
        )}
      </section>

      {/* Error */}
      {addError && (
        <div className="rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2">
          <p className="text-[12px] text-destructive">{addError}</p>
        </div>
      )}

      <FloatActions>
        <Button
          variant="ghost"
          onClick={() => {
            oauth.cancel();
            props.onCancel();
          }}
          disabled={adding}
        >
          Cancel
        </Button>
        <Button onClick={handleAdd} disabled={!canAdd || adding}>
          {adding && <Spinner className="size-3.5" />}
          {adding ? "Adding..." : "Add source"}
        </Button>
      </FloatActions>
    </div>
  );
}
