import { z } from 'zod';

import { PostgresDatabase } from '../postgres-database/postgres-database.ts';
import { Environment } from '../environment/environment.ts';

import {
  CustomResource,
  ResourceReference,
  ResourceService,
  type CustomResourceOptions,
} from '#services/resources/resources.ts';
import { API_VERSION } from '#utils/consts.ts';
import { Secret } from '#resources/core/secret/secret.ts';
import { generateRandomHexPass } from '#utils/secrets.ts';
import { Service } from '#resources/core/service/service.ts';
import { HelmRelease } from '#resources/flux/helm-release/helm-release.ts';
import { RepoService } from '#bootstrap/repos/repos.ts';
import { VirtualService } from '#resources/istio/virtual-service/virtual-service.ts';

const specSchema = z.object({
  environment: z.string(),
  subdomain: z.string().optional(),
});

type SecretData = { url: string; host: string; token: string };
type InitSecretData = {
  AUTHENTIK_BOOTSTRAP_TOKEN: string;
  AUTHENTIK_BOOTSTRAP_PASSWORD: string;
  AUTHENTIK_BOOTSTRAP_EMAIL: string;
  AUTHENTIK_SECRET_KEY: string;
};

class AuthentikServer extends CustomResource<typeof specSchema> {
  public static readonly apiVersion = API_VERSION;
  public static readonly kind = 'AuthentikServer';
  public static readonly spec = specSchema;
  public static readonly scope = 'Namespaced';

  #environment: ResourceReference<typeof Environment>;
  #database: PostgresDatabase;
  #secret: Secret<SecretData>;
  #initSecret: Secret<InitSecretData>;
  #service: Service;
  #helmRelease: HelmRelease;
  #virtualService: VirtualService;

  constructor(options: CustomResourceOptions<typeof specSchema>) {
    super(options);

    const resourceService = this.services.get(ResourceService);
    this.#environment = new ResourceReference();
    this.#environment.on('changed', this.queueReconcile);

    this.#database = resourceService.get(PostgresDatabase, this.name, this.namespace);
    this.#database.on('changed', this.queueReconcile);

    this.#secret = resourceService.get(Secret<SecretData>, this.name, this.namespace);
    this.#secret.on('changed', this.queueReconcile);

    this.#initSecret = resourceService.get(Secret<InitSecretData>, `${this.name}-init`, this.namespace);

    this.#service = resourceService.get(Service, `${this.name}-server`, this.namespace);
    // this.#service.on('changed', this.queueReconcile);

    this.#helmRelease = resourceService.get(HelmRelease, this.name, this.namespace);
    this.#helmRelease.on('changed', this.queueReconcile);

    this.#virtualService = resourceService.get(VirtualService, this.name, this.namespace);
    this.#virtualService.on('changed', this.queueReconcile);
  }

  public reconcile = async () => {
    if (!this.spec) {
      return;
    }
    const resourceService = this.services.get(ResourceService);

    this.#environment.current = resourceService.get(Environment, this.spec.environment);
    if (!this.#environment.current.spec) {
      return;
    }

    await this.#database.ensure({
      metadata: {
        ownerReferences: [this.ref],
      },
      spec: {
        environment: this.#environment.current.name,
      },
    });

    const databaseSecret = this.#database.secret.value;
    if (!databaseSecret) {
      return;
    }

    await this.#initSecret.set(
      (current) => ({
        AUTHENTIK_BOOTSTRAP_EMAIL: 'admin@example.com',
        AUTHENTIK_BOOTSTRAP_PASSWORD: generateRandomHexPass(24),
        AUTHENTIK_BOOTSTRAP_TOKEN: generateRandomHexPass(32),
        AUTHENTIK_SECRET_KEY: generateRandomHexPass(32),
        ...current,
      }),
      {
        metadata: {
          ownerReferences: [this.ref],
        },
      },
    );

    const initSecret = this.#initSecret.value;
    if (!initSecret) {
      return;
    }

    const domain = `${this.spec?.subdomain || 'authentik'}.${this.#environment.current.spec.domain}`;
    await this.#secret.set(
      {
        url: `https://${domain}`,
        host: this.#service.hostname,
        token: initSecret.AUTHENTIK_BOOTSTRAP_TOKEN,
      },
      {
        metadata: {
          ownerReferences: [this.ref],
        },
      },
    );
    const secret = this.#secret.value;
    if (!secret) {
      return;
    }

    const repoService = this.services.get(RepoService);

    await this.#helmRelease.ensure({
      metadata: {
        ownerReferences: [this.ref],
      },
      spec: {
        interval: '60m',
        chart: {
          spec: {
            chart: 'authentik',
            version: '2025.6.4',
            sourceRef: {
              apiVersion: 'source.toolkit.fluxcd.io/v1',
              kind: 'HelmRepository',
              name: repoService.authentik.name,
              namespace: repoService.authentik.namespace,
            },
          },
        },
        values: {
          global: {
            envFrom: [
              {
                secretRef: {
                  name: this.#initSecret.name,
                },
              },
            ],
          },
          authentik: {
            error_reporting: {
              enabled: false,
            },
            postgresql: {
              host: databaseSecret.host,
              name: databaseSecret.database,
              user: databaseSecret.user,
              password: 'file:///postgres-creds/password',
            },
            redis: {
              host: this.#environment.current.redisServer.service.hostname,
            },
          },
          server: {
            volumes: [
              {
                name: 'postgres-creds',
                secret: {
                  secretName: this.#database.secret.name,
                },
              },
            ],
            volumeMounts: [
              {
                name: 'postgres-creds',
                mountPath: '/postgres-creds',
                readOnly: true,
              },
            ],
          },
          worker: {
            volumes: [
              {
                name: 'postgres-creds',
                secret: {
                  secretName: this.#database.secret.name,
                },
              },
            ],
            volumeMounts: [
              {
                name: 'postgres-creds',
                mountPath: '/postgres-creds',
                readOnly: true,
              },
            ],
          },
        },
      },
    });

    const gateway = this.#environment.current.gateway;
    await this.#virtualService.set({
      metadata: {
        ownerReferences: [this.ref],
      },
      spec: {
        gateways: [`${gateway.namespace}/${gateway.name}`],
        hosts: [domain],
        http: [
          {
            route: [
              {
                destination: {
                  host: this.#service.hostname,
                  port: {
                    number: 80,
                  },
                },
              },
            ],
          },
        ],
      },
    });
  };
}

export { AuthentikServer };
