import * as process from "process";
import * as convict from "convict";
import * as Metamigo from "@digiresilience/metamigo-common";
import { defState } from "@digiresilience/montar";

export const configSchema = {
  db: {
    connection: {
      doc: "The postgres connection url.",
      format: "uri",
      default: "postgresql://metamigo:metamigo@127.0.0.1:5433/metamigo_dev",
      env: "METAMIGO_DATABASE_URL",
      sensitive: true,
    },
    name: {
      doc: "The name of the postgres database",
      format: String,
      default: "metamigo_dev",
      env: "METAMIGO_DATABASE_NAME",
    },
    owner: {
      doc: "The username of the postgres database owner",
      format: String,
      default: "metamigo",
      env: "METAMIGO_DATABASE_OWNER",
    },
  },
  worker: {
    connection: {
      doc: "The postgres connection url for the worker database.",
      format: "uri",
      default: "postgresql://metamigo:metamigo@127.0.0.1:5433/metamigo_dev",
      env: "METAMIGO_WORKER_DATABASE_URL",
    },
    concurrency: {
      doc: "The number of jobs to run concurrently",
      default: 1,
      format: "positiveInt",
      env: "METAMIGO_WORKER_CONCURRENT_JOBS",
    },
    pollInterval: {
      doc: "How long to wait between polling for jobs in milliseconds (for jobs scheduled in the future/retries)",
      default: 2000,
      format: "positiveInt",
      env: "METAMIGO_WORKER_POLL_INTERVAL_MS",
    },
  },
  postgraphile: {
    auth: {
      doc: "The postgres role that postgraphile logs in with",
      format: String,
      default: "metamigo_graphile_auth",
      env: "METAMIGO_DATABASE_AUTHENTICATOR",
    },
    appRootConnection: {
      doc: "The postgres root/superuser connection url for development mode so PG can watch the schema changes, this is strangely named in the postgraphile API 'ownerConnectionString'",
      format: String,
      default: "postgresql://postgres:metamigo@127.0.0.1:5433/metamigo_dev",
      env: "METAMIGO_APP_ROOT_DATABASE_URL",
    },
    authConnection: {
      doc: "The postgres connection URL for postgraphile, must not be superuser and must have limited privs.",
      format: String,
      default:
        "postgresql://metamigo_graphile_auth:metamigo@127.0.0.1:5433/metamigo_dev",
      env: "METAMIGO_DATABASE_AUTH_URL",
    },
    visitor: {
      doc: "The postgres role that postgraphile switches to",
      format: String,
      default: "app_postgraphile",
      env: "METAMIGO_DATABASE_VISITOR",
    },
    schema: {
      doc: "The schema postgraphile should expose with graphql",
      format: String,
      default: "app_public",
    },
    enableGraphiql: {
      doc: "Whether to enable the graphiql web interface or not",
      format: "Boolean",
      default: false,
      env: "METAMIGO_ENABLE_GRAPHIQL",
    },
  },

  dev: {
    shadowConnection: {
      doc: "The shadow databse connection url used by postgraphile-migrate. Not needed in production.",
      format: "uri",
      default: "postgresql://metamigo:metamigo@127.0.0.1:5433/metamigo_shadow",
      env: "METAMIGO_SHADOW_DATABASE_URL",
      sensitive: true,
    },
    rootConnection: {
      doc: "The postgres root/superuser connection url for testing only, database must NOT be the app database. Not needed in production.",
      format: "uri",
      default: "postgresql://postgres:metamigo@127.0.0.1:5433/template1",
      env: "METAMIGO_ROOT_DATABASE_URL",
      sensitive: true,
    },
  },
  frontend: {
    url: {
      doc: "The url the frontend can be accessed at",
      format: "url",
      default: "http://localhost:3000",
      env: "METAMIGO_FRONTEND_URL",
    },
    apiUrl: {
      doc: "The url the api backend can be accessed at from the frontend server",
      format: "url",
      default: "http://localhost:3001",
      env: "METAMIGO_API_URL",
    },
  },
  nextAuth: {
    secret: {
      doc: "A random string used to hash tokens, sign cookies and generate crytographic keys. Shared with the api backend.",
      format: String,
      default: undefined,
      env: "NEXTAUTH_SECRET",
      sensitive: true,
    },
    audience: {
      doc: "We will add this string as the `aud` claim to our JWT token, if empty or not present defaults to `frontend.url`",
      format: String,
      default: "",
      env: "NEXTAUTH_AUDIENCE",
    },
    signingKeyB64: {
      doc: "A base64 encoded JWK.Key used for JWT signing",
      format: String,
      default: undefined,
      env: "NEXTAUTH_SIGNING_KEY_B64",
      sensitive: true,
    },
    encryptionKeyB64: {
      doc: "A base64 encoded JWK.Key used for JWT encryption",
      format: String,
      default: undefined,
      env: "NEXTAUTH_ENCRYPTION_KEY_B64",
      sensitive: true,
    },
    signingKey: {
      doc: "",
      format: String,
      default: undefined,
      sensitive: true,
      skipGenerate: true,
    },
    encryptionKey: {
      doc: "",
      format: String,
      default: undefined,
      sensitive: true,
      skipGenerate: true,
    },
    google: {
      id: {
        doc: "reference https://next-auth.js.org/providers/google",
        format: String,
        default: undefined,
        env: "GOOGLE_ID",
        sensitive: true,
      },
      secret: {
        doc: "reference https://next-auth.js.org/providers/google",
        format: String,
        default: undefined,
        env: "GOOGLE_SECRET",
        sensitive: true,
      },
    },
    github: {
      id: {
        doc: "reference https://next-auth.js.org/providers/github",
        format: String,
        default: undefined,
        env: "GITHUB_ID",
        sensitive: true,
      },
      secret: {
        doc: "reference https://next-auth.js.org/providers/github",
        format: String,
        default: undefined,
        env: "GITHUB_SECRET",
        sensitive: true,
      },
    },
    gitlab: {
      id: {
        doc: "reference https://next-auth.js.org/providers/gitlab",
        format: String,
        default: undefined,
        env: "GITLAB_ID",
        sensitive: true,
      },
      secret: {
        doc: "reference https://next-auth.js.org/providers/gitlab",
        format: String,
        default: undefined,
        env: "GITLAB_SECRET",
        sensitive: true,
      },
    },
    cognito: {
      id: {
        doc: "reference https://next-auth.js.org/providers/cognito",
        format: String,
        default: undefined,
        env: "COGNITO_ID",
        sensitive: true,
      },
      secret: {
        doc: "reference https://next-auth.js.org/providers/cognito",
        format: String,
        default: undefined,
        env: "COGNITO_SECRET",
        sensitive: true,
      },
      domain: {
        doc: "reference https://next-auth.js.org/providers/cognito",
        format: String,
        default: undefined,
        env: "COGNITO_DOMAIN",
        sensitive: true,
      },
    },
  },
  cfaccess: {
    audience: {
      doc: "the cloudflare access audience id",
      format: String,
      default: undefined,
      env: "CFACCESS_AUDIENCE",
    },

    domain: {
      doc: "the cloudflare access domain, something like `YOURAPP.cloudflareaccess.com`",
      format: String,
      default: undefined,
      env: "CFACCESS_DOMAIN",
    },
  },
  signald: {
    enabled: {
      doc: "Whether to enable the signald signal backend",
      format: "Boolean",
      default: false,
      env: "SIGNALD_ENABLED",
    },
    socket: {
      doc: "the unix domain socket signald is listening on",
      format: String,
      default: `${process.cwd()}/signald/signald.sock`,
      env: "SIGNALD_SOCKET",
    },
  },
  leafcutter: {
    enabled: {
      doc: "Whether to enable leafcutter functionality",
      format: "Boolean",
      default: false,
      env: "LEAFCUTTER_ENABLED",
    },
    zammadApiUrl: {
      doc: "The full base zammad api url",
      format: String,
      default: undefined,
      env: "ZAMMAD_API_URL",
    },
    zammadApiKey: {
      doc: "The zammad api key",
      format: String,
      default: undefined,
      sensitive: true,
      env: "ZAMMAD_API_KEY",
    },
    labelStudioApiUrl: {
      doc: "The full base label studio api url",
      format: String,
      default: undefined,
      env: "LABEL_STUDIO_API_URL",
    },
    labelStudioApiKey: {
      doc: "The label studio api key",
      format: String,
      default: undefined,
      sensitive: true,
      env: "LABEL_STUDIO_API_KEY",
    },
    contributorId: {
      doc: "The leafcutter contributor id",
      format: String,
      default: undefined,
      env: "LEAFCUTTER_CONTRIBUTOR_ID",
    },
    contributorName: {
      doc: "The leafcutter contributor name",
      format: String,
      default: undefined,
      env: "LEAFCUTTER_CONTRIBUTOR_NAME",
    },
    opensearchApiUrl: {
      doc: "The opensearch api url",
      format: String,
      default: undefined,
      env: "OPENSEARCH_API_URL",
    },
    opensearchUsername: {
      doc: "The opensearch username",
      format: String,
      default: undefined,
      env: "OPENSEARCH_USERNAME",
    },
    opensearchPassword: {
      doc: "The opensearch password",
      format: String,
      default: undefined,
      sensative: true,
      env: "OPENSEARCH_PASSWORD",
    },
  },
};

// define the interfaces for the concrete config objects
export interface IDBConfig {
  connection: string;
  name: string;
  owner: string;
}

export interface IWorkerConfig {
  connection: string;
  concurrency: number;
  pollInterval: number;
}

export interface IPostgraphileConfig {
  auth: string;
  visitor: string;
  appRootConnection: string;
  authConnection: string;
  schema: string;
  enableGraphiql: boolean;
}

export interface IDevConfig {
  shadowConnection: string;
  rootConnection: string;
}

export interface IFrontendConfig {
  url: string;
  apiUrl: string;
}

export interface INextAuthConfig {
  secret: string;
  audience: string;
  signingKey: string;
  encryptionKey: string;
  signingKeyB64: string;
  encryptionKeyB64: string;
  google?: { id: string; secret: string; };
  github?: { id: string; secret: string; };
  gitlab?: { id: string; secret: string; };
  cognito?: { id: string; secret: string; domain: string; };
}

export interface ICFAccessConfig {
  audience: string;
  domain: string;
}

export interface ISignaldConifg {
  enabled: boolean;
  socket: string;
}

export interface ILeafcutterConfig {
  enabled: boolean;
  zammadApiUrl: string;
  zammadApiKey: string;
  labelStudioApiUrl: string;
  labelStudioApiKey: string;
  contributorId: string;
  contributorName: string;
  opensearchApiUrl: string;
  opensearchUsername: string;
  opensearchPassword: string;
}

// Extend the metamigo base type to add your app's custom config along side the out
// of the box Metamigo config
export interface IAppConfig extends Metamigo.IMetamigoConfig {
  db: IDBConfig;
  worker: IWorkerConfig;
  postgraphile: IPostgraphileConfig;
  dev: IDevConfig;
  frontend: IFrontendConfig;
  nextAuth: INextAuthConfig;
  cfaccess: ICFAccessConfig;
  signald: ISignaldConifg;
  leafcutter: ILeafcutterConfig;
}

export type IAppConvict = Metamigo.ExtendedConvict<IAppConfig>;

// Merge the Metamigo base schema with your app's schmea
export const schema: convict.Schema<IAppConfig> = {
  ...Metamigo.configBaseSchema,
  ...configSchema,
};

export const loadConfig = async (): Promise<IAppConfig> => {
  const config = await Metamigo.loadConfiguration(schema);

  if (!config.frontend.url || config.frontend.url === "")
    throw new Error(
      "configuration value frontend.url is missing. Add to config or set NEXTAUTH_URL env var"
    );

  // nextauth expects the url to be provided with this environment variable, so we will munge it in place here
  process.env.NEXTAUTH_URL = config.frontend.url;

  if (config.nextAuth.signingKeyB64)
    config.nextAuth.signingKey = Buffer.from(
      config.nextAuth.signingKeyB64,
      "base64"
    ).toString("utf-8");

  if (config.nextAuth.encryptionKeyB64)
    config.nextAuth.encryptionKey = Buffer.from(
      config.nextAuth.encryptionKeyB64,
      "base64"
    ).toString("utf-8");

  if (!config.nextAuth.audience || config.nextAuth.audience === "")
    config.nextAuth.audience = config.frontend.url;

  return config as IAppConfig;
};

export const loadConfigRaw = async (): Promise<IAppConvict> =>
  Metamigo.loadConfigurationRaw(schema);

const config = defState("config", {
  start: loadConfig,
});

export default config;
