import { randomBytes } from "node:crypto";

import { expect } from "@effect/vitest";
import { Effect } from "effect";
import { composePluginApi } from "@executor-js/api/server";
import {
  MICROSOFT_AUTH_TEMPLATE_SLUG,
  MICROSOFT_AUTHORIZATION_URL,
  MICROSOFT_GRAPH_DEFAULT_PRESET_IDS,
  MICROSOFT_TOKEN_URL,
} from "@executor-js/plugin-microsoft";
import { microsoftHttpPlugin } from "@executor-js/plugin-microsoft/api";
import {
  AuthTemplateSlug,
  ConnectionName,
  IntegrationSlug,
  OAuthClientSlug,
} from "@executor-js/sdk/shared";

import { scenario } from "../src/scenario";
import { Api, Target } from "../src/services";

const api = composePluginApi([microsoftHttpPlugin()] as const);

type ToolView = {
  readonly name: string;
};

const unique = (prefix: string) => `${prefix}_${randomBytes(4).toString("hex")}`;

// Compiling the ~37MB Graph spec inside dev workerd needs more headroom than
// GitHub's 2-core runners have: /api/microsoft/graph 500s and the dev stack is
// dead for every scenario after it in the shard. Local runs (and the
// production Workers streaming path) are unaffected — CI-only quarantine.
const CI_GRAPH_SPEC_SKIP = process.env.CI
  ? "compiling the full Microsoft Graph spec exhausts the 2-core CI runner and kills the dev stack for the rest of the shard"
  : undefined;

scenario(
  "Microsoft Graph: default add stores common Microsoft 365 workloads",
  { timeout: 180_000, skip: CI_GRAPH_SPEC_SKIP },
  Effect.gen(function* () {
    const target = yield* Target;
    const { client: makeApiClient } = yield* Api;
    const identity = yield* target.newIdentity();
    const client = yield* makeApiClient(api, identity);

    const integration = unique("msgraph_default");
    const connection = ConnectionName.make("main");
    const oauthClient = OAuthClientSlug.make(unique("msgraph_app"));

    yield* Effect.ensuring(
      Effect.gen(function* () {
        const added = yield* client.microsoft.addGraph({
          payload: {
            presetIds: [...MICROSOFT_GRAPH_DEFAULT_PRESET_IDS],
            customScopes: [],
            slug: integration,
            name: "Microsoft Graph Defaults",
          },
        });
        expect(added.slug, "the Microsoft Graph source keeps the requested slug").toBe(integration);
        expect(
          added.toolCount,
          "the default Microsoft Graph add extracts common user-facing operations",
        ).toBeGreaterThan(100);

        const config = yield* client.microsoft.getConfig({
          params: { slug: integration },
        });
        expect(config?.microsoftGraphPresetIds, "all default Graph groups are persisted").toEqual([
          ...MICROSOFT_GRAPH_DEFAULT_PRESET_IDS,
        ]);
        expect(
          config?.microsoftGraphCoversFullGraph,
          "the default selection is not full Graph",
        ).toBe(false);
        expect(
          config?.microsoftGraphScopes,
          "default delegated OAuth requests the selected common workload scopes",
        ).toEqual([
          "offline_access",
          "User.Read",
          "Mail.ReadWrite",
          "Mail.Send",
          "MailboxSettings.ReadWrite",
          "Calendars.ReadWrite",
          "Contacts.ReadWrite",
          "People.Read.All",
          "Tasks.ReadWrite",
          "Files.ReadWrite.All",
          "Sites.ReadWrite.All",
          "Notes.ReadWrite",
          "Chat.ReadWrite",
          "Team.ReadBasic.All",
          "Channel.ReadBasic.All",
          "ChannelMessage.Read.All",
          "ChannelMessage.Send",
          "OnlineMeetings.ReadWrite",
        ]);

        yield* client.oauth.createClient({
          payload: {
            owner: "org",
            slug: oauthClient,
            authorizationUrl: MICROSOFT_AUTHORIZATION_URL,
            tokenUrl: MICROSOFT_TOKEN_URL,
            grant: "authorization_code",
            clientId: "client-id",
            clientSecret: "client-secret",
          },
        });

        const started = yield* client.oauth.start({
          payload: {
            client: oauthClient,
            clientOwner: "org",
            owner: "org",
            name: ConnectionName.make("oauth"),
            integration: IntegrationSlug.make(integration),
            template: AuthTemplateSlug.make(MICROSOFT_AUTH_TEMPLATE_SLUG),
          },
        });
        expect(started.status, "authorization-code OAuth returns a browser redirect").toBe(
          "redirect",
        );
        const authorizationUrl = started.status === "redirect" ? started.authorizationUrl : "";
        const authorizeUrl = new URL(authorizationUrl || "https://invalid.example");
        expect(
          authorizeUrl.toString().length,
          "default Microsoft Graph OAuth authorize URLs stay under ordinary proxy limits",
        ).toBeLessThan(2_000);
        expect(
          authorizeUrl.searchParams.get("scope"),
          "default Microsoft Graph OAuth asks for common workload scopes",
        ).toBe(config?.microsoftGraphScopes?.join(" "));

        yield* client.connections.create({
          payload: {
            owner: "org",
            name: connection,
            integration: IntegrationSlug.make(integration),
            template: AuthTemplateSlug.make(MICROSOFT_AUTH_TEMPLATE_SLUG),
            value: "token-xyz",
          },
        });

        const tools = yield* client.tools.list({
          query: { integration: IntegrationSlug.make(integration), connection },
        });
        const names = tools.map((tool: ToolView) => tool.name);
        const messageTools = names.filter((name) => name.toLowerCase().includes("message"));
        const siteTools = names.filter((name) => name.toLowerCase().includes("site"));
        expect(
          messageTools,
          "the retrieved catalog includes Microsoft message operations",
        ).not.toEqual([]);
        expect(siteTools, "the retrieved catalog includes SharePoint site operations").not.toEqual(
          [],
        );
      }),
      Effect.gen(function* () {
        yield* client.connections
          .remove({
            params: {
              owner: "org",
              integration: IntegrationSlug.make(integration),
              name: connection,
            },
          })
          .pipe(Effect.ignore);
        yield* client.microsoft
          .removeGraph({ params: { slug: IntegrationSlug.make(integration) } })
          .pipe(Effect.ignore);
        yield* client.oauth
          .removeClient({
            params: { slug: oauthClient },
            payload: { owner: "org" },
          })
          .pipe(Effect.ignore);
      }),
    );
  }),
);
