import { initializeIcons } from "@fluentui/react";
import { useBoolean } from "@fluentui/react-hooks";
import { getErrorMessage } from "Common/ErrorHandlingUtils";
import { AadAuthorizationFailure } from "Platform/Hosted/Components/AadAuthorizationFailure";
import * as React from "react";
import { render } from "react-dom";
import ChevronRight from "../images/chevron-right.svg";
import "../less/hostedexplorer.less";
import { AuthType } from "./AuthType";
import { logError } from "./Common/Logger";
import { fetchAccessData, fetchEncryptedToken } from "./Common/PortalBackendClient";
import { AccessInputMetadata, DatabaseAccount } from "./Contracts/DataModels";
import "./Explorer/Menus/NavBar/MeControlComponent.less";
import { HostedExplorerChildFrame } from "./HostedExplorerChildFrame";
import { AccountSwitcher } from "./Platform/Hosted/Components/AccountSwitcher";
import { ConnectExplorer } from "./Platform/Hosted/Components/ConnectExplorer";
import { DirectoryPickerPanel } from "./Platform/Hosted/Components/DirectoryPickerPanel";
import { FeedbackCommandButton } from "./Platform/Hosted/Components/FeedbackCommandButton";
import { MeControl } from "./Platform/Hosted/Components/MeControl";
import { SignInButton } from "./Platform/Hosted/Components/SignInButton";
import "./Platform/Hosted/ConnectScreen.less";
import { parseConnectionString } from "./Platform/Hosted/Helpers/ConnectionStringParser";
import { isResourceTokenConnectionString } from "./Platform/Hosted/Helpers/ResourceTokenUtils";
import {
  extractMasterKeyFromDirectLoginConnectionString,
  isDirectConnectionStringLoginApi,
} from "./Platform/Hosted/HostedUtils";
import "./Shared/appInsights";
import { allowedHostedExplorerEndpoints } from "./Utils/EndpointUtils";
import { useAADAuth } from "./hooks/useAADAuth";
import { useConfig } from "./hooks/useConfig";

initializeIcons();

const App: React.FunctionComponent = () => {
  // For handling encrypted portal tokens sent via query paramter
  const params = new URLSearchParams(window.location.search);
  const [encryptedToken, setEncryptedToken] = React.useState<string>(params && params.get("key"));
  // Encrypted token logins resolve the account metadata through the Portal Backend, while SQL/Table/Gremlin
  // connection-string logins derive it client-side, so both paths write to the same value.
  const [accountMetadata, setAccountMetadata] = React.useState<AccessInputMetadata>();

  React.useEffect(() => {
    if (encryptedToken) {
      fetchAccessData(encryptedToken).then(setAccountMetadata);
    }
  }, [encryptedToken]);

  // For showing/hiding panel
  const [isOpen, { setTrue: openPanel, setFalse: dismissPanel }] = useBoolean(false);
  const config = useConfig();
  const { isLoggedIn, armToken, graphToken, account, tenantId, logout, login, switchTenant, authFailure } =
    useAADAuth(config);

  const [databaseAccount, setDatabaseAccount] = React.useState<DatabaseAccount>();
  const [authType, setAuthType] = React.useState<AuthType>(encryptedToken ? AuthType.EncryptedToken : undefined);
  const [connectionString, setConnectionString] = React.useState<string>();

  const ref = React.useRef<HTMLIFrameElement>();

  const connectWithConnectionString = React.useCallback(
    (connStr: string) => {
      if (!connStr || authType) {
        return;
      }
      setConnectionString(connStr);
      if (isResourceTokenConnectionString(connStr)) {
        setAuthType(AuthType.ResourceToken);
        return;
      }

      const metadata = parseConnectionString(connStr);
      if (metadata && isDirectConnectionStringLoginApi(metadata.apiKind)) {
        // SQL, Table, and Gremlin sign data-plane requests client-side with the account key, so we skip
        // the Portal Backend proxy and use the metadata derived from the connection string directly.
        setAccountMetadata(metadata);
        setAuthType(AuthType.ConnectionString);
        return;
      }

      fetchEncryptedToken(connStr)
        .then((token) => {
          setEncryptedToken(token);
          setAuthType(AuthType.ConnectionString);
        })
        .catch((error) => {
          logError(
            `Failed to connect with connection string: ${getErrorMessage(error)}`,
            "HostedExplorer/connectWithConnectionString",
          );
        });
    },
    [authType],
  );

  // Listen for connection string sent via postMessage (from TryCosmosDB)
  React.useEffect(() => {
    const MSG_READY = "tryCosmosDBReady";
    const MSG_CONNECTION_STRING = "tryCosmosDBConnectionString";

    // Signal to the opener that we are ready to receive a connection string
    if (window.opener) {
      try {
        for (const origin of allowedHostedExplorerEndpoints) {
          window.opener.postMessage({ type: MSG_READY }, origin);
        }
      } catch {
        // opener may be cross-origin, ignore
      }
    }

    const handler = (event: MessageEvent) => {
      if (!allowedHostedExplorerEndpoints.includes(event.origin)) {
        return;
      }
      if (event.data?.type === MSG_CONNECTION_STRING) {
        const connStr: string = event.data.connectionString;
        if (parseConnectionString(connStr)) {
          connectWithConnectionString(connStr);
        }
      }
    };

    window.addEventListener("message", handler);
    return () => window.removeEventListener("message", handler);
  }, [connectWithConnectionString]);

  React.useEffect(() => {
    // If ref.current is undefined no iframe has been rendered
    if (ref.current) {
      // In hosted mode, we can set global properties directly on the child iframe.
      // This is not possible in the portal where the iframes have different origins
      const frameWindow = ref.current.contentWindow as HostedExplorerChildFrame;

      if (authType === AuthType.EncryptedToken) {
        frameWindow.hostedConfig = {
          authType: AuthType.EncryptedToken,
          encryptedToken,
          encryptedTokenMetadata: accountMetadata,
        };
      } else if (authType === AuthType.ConnectionString) {
        frameWindow.hostedConfig = {
          authType: AuthType.ConnectionString,
          encryptedToken,
          encryptedTokenMetadata: accountMetadata,
          masterKey: extractMasterKeyFromDirectLoginConnectionString(connectionString),
        };
      } else if (authType === AuthType.ResourceToken) {
        frameWindow.hostedConfig = {
          authType: AuthType.ResourceToken,
          resourceToken: connectionString,
        };
      } else if (isLoggedIn && !connectionString) {
        frameWindow.hostedConfig = {
          authType: AuthType.AAD,
          databaseAccount,
          authorizationToken: armToken,
        };
      }
    }
  });

  const showExplorer =
    (config && isLoggedIn && databaseAccount && !connectionString) ||
    accountMetadata ||
    (authType === AuthType.ResourceToken && connectionString);

  return (
    <>
      <header>
        <div className="items" role="menubar">
          <div className="cosmosDBTitle">
            <span
              className="title"
              onClick={() => window.open("https://portal.azure.com", "_blank")}
              tabIndex={0}
              title="Go to Azure Portal"
            >
              Microsoft Azure
            </span>
            <span className="accontSplitter" /> <span className="serviceTitle">Cosmos DB</span>
            {(isLoggedIn || accountMetadata?.accountName) && (
              <img className="chevronRight" src={ChevronRight} alt="account separator" />
            )}
            {isLoggedIn && !connectionString && (
              <span className="accountSwitchComponentContainer">
                <AccountSwitcher armToken={armToken} setDatabaseAccount={setDatabaseAccount} />
              </span>
            )}
            {(!isLoggedIn || connectionString) && accountMetadata?.accountName && (
              <span className="accountSwitchComponentContainer">
                <span className="accountNameHeader">{accountMetadata?.accountName}</span>
              </span>
            )}
          </div>
          <FeedbackCommandButton />
          <div className="meControl">
            {isLoggedIn ? (
              <MeControl {...{ graphToken, openPanel, logout, account }} />
            ) : (
              <SignInButton {...{ login }} />
            )}
          </div>
        </div>
      </header>
      {showExplorer && (
        // Ideally we would import and render data explorer like any other React component, however
        // because it still has a significant amount of Knockout code, this would lead to memory leaks.
        // Knockout does not have a way to tear down all of its binding and listeners with a single method.
        // It's possible this can be changed once all knockout code has been removed.
        <iframe
          // Setting key is needed so React will re-render this element on any account change
          key={authType ? `${authType}-${accountMetadata?.accountName || connectionString}` : databaseAccount?.id}
          ref={ref}
          data-test="DataExplorerFrame"
          id="explorerMenu"
          name="explorer"
          className="iframe"
          title="explorer"
          src="explorer.html?v=1.0.1&platform=Hosted"
        ></iframe>
      )}
      {!isLoggedIn && !accountMetadata && (
        <ConnectExplorer
          {...{
            login,
            setEncryptedToken,
            setAuthType,
            connectionString,
            setConnectionString,
            setAccountMetadata,
          }}
        />
      )}
      {isLoggedIn && authFailure && <AadAuthorizationFailure {...{ authFailure }} />}
      {isLoggedIn && !authFailure && (
        <DirectoryPickerPanel {...{ isOpen, dismissPanel, armToken, tenantId, switchTenant }} />
      )}
    </>
  );
};

export { App };

const appElement = document.getElementById("App");
if (appElement) {
  render(<App />, appElement);
}
