import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource, EntityManager } from 'typeorm';
import { AuditCodeSequence } from '../entities/audit-code-sequence.entity';
import { AuditType } from '../entities/audit-schedule-row.entity';

@Injectable()
export class AuditCodeGeneratorService {
  // INITIAL → INI, SURVEILLANCE → SUR, RECERTIFICATION → REC
  private readonly TYPE_PREFIX: Record<AuditType, string> = {
    [AuditType.INITIAL]: 'INI',
    [AuditType.SURVEILLANCE]: 'SUR',
    [AuditType.RECERTIFICATION]: 'REC',
  };

  constructor(
    @InjectDataSource('scheme_dbs')
    private readonly dataSource: DataSource,
  ) {}

  async generate(
    auditType: AuditType,
    auditDate: Date | string,
    manager?: EntityManager,
  ): Promise<string> {
    const prefix = this.TYPE_PREFIX[auditType];
    if (!prefix) {
      throw new Error(`Unknown audit type: ${auditType}`);
    }

    // 🛠 FIX: was `new Date(auditDate)`. When auditDate is a date-only string
    // like "2026-07-21", JS parses it as UTC midnight, but the getters below
    // read it back in local time — on a server whose TZ isn't UTC this can
    // silently generate the audit code under the wrong month/day. Parsing
    // the Y/M/D components by hand and building a local Date avoids the
    // UTC round-trip entirely.
    const date =
      typeof auditDate === 'string'
        ? (() => {
            const [y, m, d] = auditDate.split('-').map((v) => parseInt(v, 10));
            return new Date(y, (m || 1) - 1, d || 1);
          })()
        : auditDate;
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();

    const run = async (m: EntityManager): Promise<string> => {
      const repo = m.getRepository(AuditCodeSequence);

      // Try to lock existing row for this (type, year, month)
      let seq = await repo
        .createQueryBuilder('seq')
        .setLock('pessimistic_write')
        .where('seq.audit_type = :t', { t: prefix })
        .andWhere('seq.year = :y', { y: year })
        .andWhere('seq.month = :mo', { mo: month })
        .getOne();

      if (!seq) {
       
        await m.query(
          `INSERT INTO audit_code_sequences (audit_type, year, month, last_number)
           VALUES (?, ?, ?, 0)
           ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id)`,
          [prefix, year, month],
        );

        // Re-fetch with lock
        seq = await repo
          .createQueryBuilder('seq')
          .setLock('pessimistic_write')
          .where('seq.audit_type = :t', { t: prefix })
          .andWhere('seq.year = :y', { y: year })
          .andWhere('seq.month = :mo', { mo: month })
          .getOne();

      
        if (!seq) {
          throw new Error(
            `Failed to load audit_code_sequence after insert for ${prefix}-${year}-${month}`,
          );
        }
      }

      seq.last_number += 1;
      await repo.save(seq);

      const sequenceStr = String(seq.last_number).padStart(6, '0');
      const yyyy = String(year);
      const mm = String(month).padStart(2, '0');
      const dd = String(day).padStart(2, '0');

      return `${prefix}-${yyyy}-${mm}-${dd}-${sequenceStr}`;
    };

    if (manager) {
      // Caller is already inside a transaction — reuse it
      return run(manager);
    }

    return this.dataSource.transaction(run);
  }
}
