import { createRequire } from "node:module";

import { Effect } from "effect";

import { KeychainError } from "./errors";

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const DEFAULT_SERVICE_NAME = "executor";
const SERVICE_NAME_ENV = "EXECUTOR_KEYCHAIN_SERVICE_NAME";

// ---------------------------------------------------------------------------
// Platform helpers
// ---------------------------------------------------------------------------

export const isSupportedPlatform = () =>
  process.platform === "darwin" || process.platform === "linux" || process.platform === "win32";

export const displayName = () =>
  process.platform === "darwin"
    ? "macOS Keychain"
    : process.platform === "win32"
      ? "Windows Credential Manager"
      : "Desktop Keyring";

export const resolveServiceName = (explicit?: string): string =>
  explicit?.trim() || process.env[SERVICE_NAME_ENV]?.trim() || DEFAULT_SERVICE_NAME;

// ---------------------------------------------------------------------------
// Lazy-load @napi-rs/keyring (native module)
// ---------------------------------------------------------------------------

type EntryConstructor = (typeof import("@napi-rs/keyring"))["Entry"];

let entryCtorPromise: Promise<EntryConstructor> | null = null;

// In compiled bun binaries (`bun build --compile`) `.node` modules aren't
// included in bunfs and there's no node_modules at runtime, so
// @napi-rs/keyring's loader can't find its platform-specific binding.
// `apps/cli/src/build.ts` copies the .node next to the executor and
// `apps/cli/src/main.ts` exports its absolute path here. We load it
// directly because @napi-rs/keyring@1.2.0's NAPI_RS_NATIVE_LIBRARY_PATH
// branch is buggy (assigns to a local that gets overwritten before return).
const loadEntryCtor = async (): Promise<EntryConstructor> => {
  const directPath = process.env.EXECUTOR_KEYRING_NATIVE_PATH;
  if (directPath) {
    const req = createRequire(import.meta.url);
    return (req(directPath) as { Entry: EntryConstructor }).Entry;
  }
  const { Entry } = await import("@napi-rs/keyring");
  return Entry;
};

const loadEntry = (): Effect.Effect<EntryConstructor, KeychainError> =>
  isSupportedPlatform()
    ? Effect.tryPromise({
        try: async () => {
          entryCtorPromise ??= loadEntryCtor();
          return await entryCtorPromise;
        },
        catch: (cause) =>
          new KeychainError({
            message: "Failed loading native keyring",
            cause,
          }),
      })
    : Effect.fail(
        new KeychainError({
          message: `Failed loading native keyring: unsupported platform '${process.platform}'`,
        }),
      );

const createEntry = (serviceName: string, account: string) =>
  Effect.flatMap(loadEntry(), (Entry) =>
    Effect.try({
      try: () => new Entry(serviceName, account),
      catch: (cause) =>
        new KeychainError({
          message: "Failed creating keyring entry",
          cause,
        }),
    }),
  );

// ---------------------------------------------------------------------------
// Low-level keychain operations
// ---------------------------------------------------------------------------

export const getPassword = (
  serviceName: string,
  account: string,
): Effect.Effect<string | null, KeychainError> =>
  Effect.flatMap(createEntry(serviceName, account), (entry) =>
    Effect.try({
      try: () => entry.getPassword(),
      catch: () => new KeychainError({ message: `Failed reading secret for account '${account}'` }),
    }),
  );

export const setPassword = (
  serviceName: string,
  account: string,
  value: string,
): Effect.Effect<void, KeychainError> =>
  Effect.flatMap(createEntry(serviceName, account), (entry) =>
    Effect.try({
      try: () => entry.setPassword(value),
      catch: (cause) =>
        new KeychainError({
          message: "Failed writing secret",
          cause,
        }),
    }).pipe(Effect.asVoid),
  );

export const deletePassword = (
  serviceName: string,
  account: string,
): Effect.Effect<boolean, KeychainError> =>
  Effect.flatMap(createEntry(serviceName, account), (entry) =>
    Effect.try({
      try: () => {
        entry.deletePassword();
        return true;
      },
      catch: () =>
        new KeychainError({ message: `Failed deleting secret for account '${account}'` }),
    }),
  );
