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

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

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

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

const LOG_LEVELS = ["trace", "debug", "info", "warn", "error", "fatal"] as const;
const ROTATION_FREQUENCIES = ["hourly", "daily", "weekly"] as const;

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)),
      probes: Type.Optional(createProbesSchema()),
      server: Type.Optional(createServerSchema()),
      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[]]);
}

function createLoggingSchema(): TSchema {
  const logLevelSchema = Type.Union(LOG_LEVELS.map((l) => Type.Literal(l)) as unknown as [TSchema, ...TSchema[]]);
  return Type.Object(
    {
      console: Type.Optional(Type.Object({ level: Type.Optional(logLevelSchema) }, { additionalProperties: false })),
      file: Type.Optional(
        Type.Object(
          {
            level: Type.Optional(logLevelSchema),
            path: Type.Optional(Type.String({ minLength: 1 })),
            rotation: Type.Optional(
              Type.Object(
                {
                  frequency: Type.Optional(
                    Type.Union(ROTATION_FREQUENCIES.map((f) => Type.Literal(f)) as unknown as [TSchema, ...TSchema[]]),
                  ),
                  maxFiles: Type.Optional(Type.Integer({ minimum: 1 })),
                  size: Type.Optional(sizeSchema),
                },
                { additionalProperties: false },
              ),
            ),
          },
          { additionalProperties: false },
        ),
      ),
      level: Type.Optional(logLevelSchema),
    },
    { additionalProperties: false },
  );
}

function createProbesSchema(): TSchema {
  return Type.Object(
    {
      execution: Type.Optional(
        Type.Object(
          {
            maxConcurrentChecks: Type.Optional(Type.Integer({ minimum: 1 })),
          },
          { additionalProperties: false },
        ),
      ),
    },
    { additionalProperties: false },
  );
}

function createServerSchema(): TSchema {
  return Type.Object(
    {
      listen: Type.Optional(
        Type.Object(
          {
            host: Type.Optional(Type.String()),
            port: Type.Optional(Type.Integer({ maximum: 65535, minimum: 0 })),
          },
          { additionalProperties: false },
        ),
      ),
      logging: Type.Optional(createLoggingSchema()),
      storage: Type.Optional(
        Type.Object(
          {
            dataDir: Type.Optional(Type.String()),
            retention: Type.Optional(durationSchema),
          },
          { additionalProperties: false },
        ),
      ),
    },
    { additionalProperties: false },
  );
}
