import {
  Injectable,
  OnApplicationShutdown,
  OnModuleInit,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as sql from 'mssql';
import {
  getRequiredBoolean,
  getRequiredNumber,
} from 'src/common/utils/config.util';
import { PinoLogger } from 'nestjs-pino';

@Injectable()
export class MssqlService implements OnModuleInit, OnApplicationShutdown {
  constructor(
    private readonly config: ConfigService,
    private readonly logger: PinoLogger,
  ) {
    this.logger.setContext(MssqlService.name);
  }
  private pool: sql.ConnectionPool | null = null;

  async onModuleInit(): Promise<void> {
    this.pool = await this.createPool();
    this.logger.info(
      { event: 'infra.mssql.pool', operation: 'initialized' },
      'MSSQL pool initialized',
    );
  }

  getPool(): sql.ConnectionPool {
    if (!this.pool) {
      throw new Error('MSSQL connection pool is not initialized');
    }

    return this.pool;
  }

  request(): sql.Request {
    return this.getPool().request();
  }

  async onApplicationShutdown(signal?: string): Promise<void> {
    if (!this.pool) {
      return;
    }

    this.logger.info(
      {
        event: 'infra.mssql.pool',
        operation: 'shutdown',
        signal: signal ?? null,
      },
      'closing MSSQL pool',
    );

    await this.pool.close();
    this.pool = null;
  }

  private async createPool(): Promise<sql.ConnectionPool> {
    const pool = new sql.ConnectionPool({
      server: this.config.getOrThrow<string>('MSSQL_HOST'),
      port: getRequiredNumber(this.config, 'MSSQL_PORT'),
      user: this.config.getOrThrow<string>('MSSQL_USER'),
      password: this.config.getOrThrow<string>('MSSQL_PASSWORD'),
      database: this.config.getOrThrow<string>('MSSQL_DB'),
      options: {
        encrypt: getRequiredBoolean(this.config, 'MSSQL_ENCRYPT'),
        trustServerCertificate: getRequiredBoolean(
          this.config,
          'MSSQL_TRUST_CERT',
        ),
        enableArithAbort: true,
      },
      pool: {
        max: getRequiredNumber(this.config, 'MSSQL_POOL_MAX'),
        min: getRequiredNumber(this.config, 'MSSQL_POOL_MIN'),
        idleTimeoutMillis: getRequiredNumber(
          this.config,
          'MSSQL_POOL_IDLE_TIMEOUT',
        ),
      },
      requestTimeout: getRequiredNumber(this.config, 'MSSQL_REQUEST_TIMEOUT'),
    });

    return pool.connect();
  }
}
