import { type } from "arktype";

import log from "~/utils/log";

import DockerIntegration from "./integration/docker";
import KubernetesIntegration from "./integration/kubernetes";
import ProcIntegration from "./integration/proc";
import { deprecatedField } from "./utils";

export const pathSupportedKeys = [
  "server.cookie_secret",
  "oidc.client_secret",
  "oidc.headscale_api_key",
  "integration.agent.pre_authkey",
] as const;

const serverConfig = type({
  host: 'string.ip = "0.0.0.0"',
  port: "number.integer = 3000",
  base_url: "string.url?",
  data_path: 'string.lower = "/var/lib/headplane/"',
  info_secret: "string?",

  cookie_secret: "(32 <= string <= 32)",
  cookie_secure: "boolean = true",
  cookie_domain: "string.lower?",
  cookie_max_age: "number.integer = 86400",
});

const partialServerConfig = type({
  host: "string.ip?",
  port: "number.integer?",
  base_url: "string.url?",
  data_path: "string.lower?",
  info_secret: "string?",

  cookie_secret: "(32 <= string <= 32)?",
  cookie_secure: "boolean?",
  cookie_domain: "string.lower?",
  cookie_max_age: "number.integer?",
});

const headscaleConfig = type({
  url: type("string.url").pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v)),
  public_url: type("string.url")
    .pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
    .optional(),
  config_path: "string.lower?",
  config_strict: "boolean = true",
  dns_records_path: "string.lower?",
  tls_cert_path: "string.lower?",
});

const partialHeadscaleConfig = type({
  url: type("string.url")
    .pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
    .optional(),
  public_url: type("string.url")
    .pipe((v) => (v.endsWith("/") ? v.slice(0, -1) : v))
    .optional(),
  config_path: "string.lower?",
  config_strict: "boolean?",
  dns_records_path: "string.lower?",
  tls_cert_path: "string.lower?",
});

const oidcConfig = type({
  enabled: "boolean = true",
  issuer: "string.url",
  client_id: "string",
  client_secret: "string",
  headscale_api_key: "string",
  use_pkce: "boolean = false",
  redirect_uri: type("string.url")
    .pipe((value, ctx) => {
      log.warn("config", "%s is deprecated and will be removed in 0.7.0", ctx.propString);

      const cleanedValue = new URL(value.trim());
      if (cleanedValue.pathname.endsWith(`${__PREFIX__}/oidc/callback`)) {
        cleanedValue.pathname = cleanedValue.pathname.replace(`${__PREFIX__}/oidc/callback`, "/");

        log.warn(
          "config",
          'Please migrate to using `server.base_url` with a value of "%s"',
          cleanedValue.toString(),
        );
      }

      return cleanedValue.toString();
    })
    .optional(),
  disable_api_key_login: "boolean = false",
  scope: 'string = "openid email profile"',
  profile_picture_source: '"oidc" | "gravatar" = "oidc"',
  extra_params: "Record<string, string>?",

  authorization_endpoint: "string.url?",
  token_endpoint: "string.url?",
  userinfo_endpoint: "string.url?",
  token_endpoint_auth_method: '"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',

  // Old/deprecated options
  strict_validation: type("unknown").narrow(deprecatedField()).optional(),
});

const partialOidcConfig = type({
  enabled: "boolean?",
  issuer: "string.url?",
  client_id: "string?",
  client_secret: "string?",
  use_pkce: "boolean?",
  headscale_api_key: "string?",
  redirect_uri: "string.url?",
  disable_api_key_login: "boolean?",
  scope: "string?",
  extra_params: "Record<string, string>?",
  profile_picture_source: '"oidc" | "gravatar"?',

  authorization_endpoint: "string.url?",
  token_endpoint: "string.url?",
  userinfo_endpoint: "string.url?",
  token_endpoint_auth_method: '"client_secret_basic" | "client_secret_post" | "client_secret_jwt"?',

  // Old/deprecated options
  strict_validation: type("unknown").narrow(deprecatedField()).optional(),
});

const agentConfig = type({
  enabled: "boolean",
  host_name: 'string = "headplane-agent"',
  pre_authkey: "string",
  cache_ttl: "number.integer = 180000",
  cache_path: 'string = "/var/lib/headplane/agent_cache.json"',
  executable_path: 'string = "/usr/libexec/headplane/agent"',
  work_dir: 'string = "/var/lib/headplane/agent"',
});

const partialAgentConfig = type({
  enabled: "boolean?",
  host_name: "string?",
  pre_authkey: "string?",
  cache_ttl: "number.integer?",
  cache_path: "string?",
  executable_path: "string?",
  work_dir: "string?",
});

const integrationConfig = type({
  docker: DockerIntegration.configSchema.full,
  kubernetes: KubernetesIntegration.configSchema.full,
  proc: ProcIntegration.configSchema.full,
  agent: agentConfig.optional(),
}).partial();

export const partialIntegrationConfig = type({
  docker: DockerIntegration.configSchema.partial,
  kubernetes: KubernetesIntegration.configSchema.partial,
  proc: ProcIntegration.configSchema.partial,
  agent: partialAgentConfig.optional(),
}).partial();

export const headplaneConfig = type({
  debug: "boolean = false",
  server: serverConfig,
  headscale: headscaleConfig,
  oidc: oidcConfig.optional(),
  integration: integrationConfig.optional(),
}).onDeepUndeclaredKey("delete");

export const partialHeadplaneConfig = type({
  debug: "boolean?",
  server: partialServerConfig.optional(),
  headscale: partialHeadscaleConfig.optional(),
  oidc: partialOidcConfig.optional(),
  integration: partialIntegrationConfig.optional(),
});

export type HeadplaneConfig = typeof headplaneConfig.infer;
export type PartialHeadplaneConfig = typeof partialHeadplaneConfig.infer;

type DotNotationToObjects<T extends string, V> = T extends `${infer K}.${infer Rest}`
  ? { [P in K]?: DotNotationToObjects<Rest, V> }
  : { [P in `${T}_path`]?: V };

type ObjectDeepMerge<T> = T extends object
  ? {
      [K in keyof T]: T[K] extends object ? ObjectDeepMerge<T[K]> : T[K];
    }
  : T;

type ConfigWithPathKeys = ObjectDeepMerge<
  DotNotationToObjects<(typeof pathSupportedKeys)[number], string | undefined>
>;

export type PartialHeadplaneConfigWithPaths = PartialHeadplaneConfig & ConfigWithPathKeys;
