import type { TSchema } from "@sinclair/typebox";

import { Type } from "@sinclair/typebox";

import type { CheckerDefinition } from "../runner/types";

import {
  createRawContentExpectationsSchema,
  createRawKeyedExpectationsSchema,
  createRawValueExpectationSchema,
  createValueMatcherObjectSchema,
  durationSchema,
  variableValueSchema,
} from "./fragments";

export function createExternalProbeConfigSchema(checkers: CheckerDefinition[]): Record<string, unknown> {
  return {
    ...cloneSchema(createProbeConfigSchema(checkers, true)),
    $id: "https://dial.local/probe-config.schema.json",
    $schema: "http://json-schema.org/draft-07/schema#",
    definitions: {
      ContentExpectations: cloneSchema(createRawContentExpectationsSchema()),
      KeyedExpectations: cloneSchema(createRawKeyedExpectationsSchema()),
      ValueExpectation: cloneSchema(createRawValueExpectationSchema()),
      ValueMatcher: cloneSchema(createValueMatcherObjectSchema()),
    },
  };
}

export function createProbeConfigSchema(checkers: CheckerDefinition[], external = false): TSchema {
  return Type.Object(
    {
      defaults: Type.Optional(createDefaultsSchema(checkers)),
      runtime: Type.Optional(
        Type.Object(
          {
            maxConcurrentChecks: Type.Optional(Type.Integer({ minimum: 1 })),
            retention: Type.Optional(durationSchema),
          },
          { additionalProperties: false },
        ),
      ),
      server: Type.Optional(
        Type.Object(
          {
            dataDir: Type.Optional(Type.String()),
            host: Type.Optional(Type.String()),
            port: Type.Optional(Type.Integer({ maximum: 65535, minimum: 0 })),
          },
          { additionalProperties: false },
        ),
      ),
      targets: Type.Array(external ? createExternalTargetSchema(checkers) : createBaseTargetSchema(checkers), {
        minItems: 1,
      }),
      variables: Type.Optional(Type.Record(Type.String({ pattern: "^[a-zA-Z_][a-zA-Z0-9_]*$" }), variableValueSchema)),
    },
    { additionalProperties: false },
  );
}

export function createTargetSchema(checker: CheckerDefinition): TSchema {
  const properties: Record<string, TSchema> = {
    description: Type.Optional(Type.Union([Type.Null(), Type.String({ maxLength: 500 })])),
    expect: Type.Optional(checker.schemas.expect),
    group: Type.Optional(Type.String()),
    id: Type.String({ maxLength: 30, minLength: 1 }),
    interval: Type.Optional(durationSchema),
    name: Type.Optional(Type.Union([Type.Null(), Type.String({ maxLength: 30, minLength: 1 })])),
    timeout: Type.Optional(durationSchema),
    type: Type.Literal(checker.type),
  };
  properties[checker.configKey] = checker.schemas.config;
  return Type.Object(properties, { additionalProperties: false });
}

function cloneSchema(schema: TSchema): Record<string, unknown> {
  return JSON.parse(JSON.stringify(schema)) as Record<string, unknown>;
}

function createBaseTargetSchema(checkers: CheckerDefinition[]): TSchema {
  return Type.Object(
    {
      description: Type.Optional(Type.Union([Type.Null(), Type.String({ maxLength: 500 })])),
      group: Type.Optional(Type.String()),
      id: Type.String({ maxLength: 30, minLength: 1 }),
      interval: Type.Optional(durationSchema),
      name: Type.Optional(Type.Union([Type.Null(), Type.String({ maxLength: 30, minLength: 1 })])),
      timeout: Type.Optional(durationSchema),
      type: Type.Union(checkers.map((checker) => Type.Literal(checker.type)) as unknown as [TSchema, ...TSchema[]]),
    },
    { additionalProperties: true },
  );
}

function createDefaultsSchema(checkers: CheckerDefinition[]): TSchema {
  const properties: Record<string, TSchema> = {
    interval: Type.Optional(durationSchema),
    timeout: Type.Optional(durationSchema),
  };
  for (const checker of checkers) {
    properties[checker.configKey] = Type.Optional(checker.schemas.defaults);
  }
  return Type.Object(properties, { additionalProperties: false });
}

function createExternalTargetSchema(checkers: CheckerDefinition[]): TSchema {
  return Type.Union(checkers.map((checker) => createTargetSchema(checker)) as [TSchema, ...TSchema[]]);
}
