import { ImmichLogger } from '@app/infra/logger';
import { Inject, Injectable } from '@nestjs/common';
import { QueryFailedError } from 'typeorm';
import { Version } from '../domain.constant';
import { DatabaseExtension, IDatabaseRepository } from '../repositories';

@Injectable()
export class DatabaseService {
  private logger = new ImmichLogger(DatabaseService.name);
  minPostgresVersion = 14;
  minVectorsVersion = new Version(0, 1, 1);
  maxVectorsVersion = new Version(0, 1, 11);

  constructor(@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository) {}

  async init() {
    await this.assertPostgresql();
    await this.createVectors();
    await this.assertVectors();
    await this.databaseRepository.runMigrations();
  }

  private async assertVectors() {
    const version = await this.databaseRepository.getExtensionVersion(DatabaseExtension.VECTORS);
    if (version == null) {
      throw new Error('Unexpected: The pgvecto.rs extension is not installed.');
    }

    const image = await this.getVectorsImage();
    const suggestion = image ? `, such as with the docker image '${image}'` : '';

    if (version.isEqual(new Version(0, 0, 0))) {
      throw new Error(
        `The pgvecto.rs extension version is ${version}, which means it is a nightly release.` +
          `Please run 'DROP EXTENSION IF EXISTS vectors' and switch to a release version${suggestion}.`,
      );
    }

    if (version.isNewerThan(this.maxVectorsVersion)) {
      throw new Error(`
        The pgvecto.rs extension version is ${version} instead of ${this.maxVectorsVersion}.
        Please run 'DROP EXTENSION IF EXISTS vectors' and switch to ${this.maxVectorsVersion}${suggestion}.`);
    }

    if (version.isOlderThan(this.minVectorsVersion)) {
      throw new Error(`
        The pgvecto.rs extension version is ${version}, which is older than the minimum supported version ${this.minVectorsVersion}.
        Please upgrade to this version or later${suggestion}.`);
    }
  }

  private async createVectors() {
    await this.databaseRepository.createExtension(DatabaseExtension.VECTORS).catch(async (err: QueryFailedError) => {
      const image = await this.getVectorsImage();
      this.logger.fatal(`
        Failed to create pgvecto.rs extension.
        If you have not updated your Postgres instance to a docker image that supports pgvecto.rs (such as '${image}'), please do so.
        See the v1.91.0 release notes for more info: https://github.com/immich-app/immich/releases/tag/v1.91.0'
      `);
      throw err;
    });
  }

  private async getVectorsImage() {
    const { major } = await this.databaseRepository.getPostgresVersion();
    if (![14, 15, 16].includes(major)) {
      return null;
    }
    return `tensorchord/pgvecto-rs:pg${major}-v${this.maxVectorsVersion}`;
  }

  private async assertPostgresql() {
    const { major } = await this.databaseRepository.getPostgresVersion();
    if (major < this.minPostgresVersion) {
      throw new Error(`
        The PostgreSQL version is ${major}, which is older than the minimum supported version ${this.minPostgresVersion}.
        Please upgrade to this version or later.`);
    }
  }
}
