import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository, InjectDataSource } from '@nestjs/typeorm';
import { Repository, DataSource } from 'typeorm';
import {
  CompanyAudit,
  AuditMode,
  AuditStatus,
  AuditPhase,
} from './entities/company-audit.entity';
import { Company } from '../companies/entities/company.entity';
import { AuditType } from '../audit-types/entities/audit-type.entity';
import { Standard } from '../standards/entities/standard.entity';
import { CreateCompanyAuditDto } from './dto/create-company-audit.dto';
import { UpdateCompanyAuditDto } from './dto/update-company-audit.dto';

@Injectable()
export class CompanyAuditsService {
  [x: string]: any;
  constructor(
    @InjectRepository(CompanyAudit, 'certification_db')
    private readonly auditRepository: Repository<CompanyAudit>,

    @InjectRepository(Company, 'certification_db')
    private readonly companyRepository: Repository<Company>,

    @InjectRepository(AuditType, 'certification_db')
    private readonly auditTypeRepository: Repository<AuditType>,

    @InjectRepository(Standard, 'certification_db')
    private readonly standardRepository: Repository<Standard>,

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

  // --- Generate unique audit code ---
  private async generateAuditCode(
    company: Company,
    auditType: AuditType,
    standard: Standard,
  ): Promise<string> {
    const prefix = 'AUD';
    const initials = company.name
      .split(' ')
      .filter((w) => w.length > 2)
      .map((w) => w[0].toUpperCase())
      .join('')
      .substring(0, 4);

    const date = new Date();
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, '0');
    const day = date.getDate().toString().padStart(2, '0');

    let seq = 1;

    const lastAudit = await this.auditRepository.findOne({
      where: {
        company: { id: company.id },
        auditType: { id: auditType.id },
        standard: { id: standard.id },
      },
      order: { createdAt: 'DESC' },
    });

    if (lastAudit) {
      const match = lastAudit.auditCode.match(/(\d+)$/);
      if (match) seq = parseInt(match[1], 10) + 1;
    }

    return `${prefix}-${initials}-${year}-${month}-${day}-${auditType.name
      .substring(0, 2)
      .toUpperCase()}-${seq.toString().padStart(3, '0')}`;
  }

  // --- Create Audit (single or multiple standards) ---
  async create(dto: CreateCompanyAuditDto): Promise<CompanyAudit[]> {
    return await this.dataSource.transaction(async (manager) => {
      const company = await manager.findOne(Company, {
        where: { id: dto.companyId },
        relations: ['standards'],
      });
      if (!company) throw new NotFoundException('Company not found');

      const auditType = await manager.findOne(AuditType, {
        where: { id: dto.auditTypeId },
      });
      if (!auditType) throw new NotFoundException('Audit type not found');

      // Determine standards
      let standards: Standard[] = [];
      if (dto.standardIds && dto.standardIds.length > 0) {
        standards = await manager.findByIds(Standard, dto.standardIds);
        if (standards.length === 0)
          throw new NotFoundException('No valid standards found');
      } else if (dto.standardId) {
        const standard = await manager.findOne(Standard, {
          where: { id: dto.standardId },
        });
        if (!standard) throw new NotFoundException('Standard not found');
        standards = [standard];
      } else if (company.standards && company.standards.length > 0) {
        standards = [company.standards[company.standards.length - 1]];
      } else {
        throw new NotFoundException('No standard linked to this company');
      }

      // Generate a single audit code using the first standard
      const auditCode = await this.generateAuditCode(
        company,
        auditType,
        standards[0],
      );

      const auditData: Partial<CompanyAudit> = {
        company,
        auditType,
        standard: standards[0], // backward compatibility
        standards, // attach all standards here
        auditCode,
        auditMode: dto.auditMode ?? AuditMode.PHYSICAL,
        validityPeriod: dto.validityPeriod,
        status: dto.status ?? AuditStatus.SCHEDULED,
        currentStage: dto.currentStage,
        remarks: dto.remarks,
      };

      const audit = manager.create(CompanyAudit, auditData);
      const savedAudit = await manager.save(audit);

      return [savedAudit]; // always return array
    });
  }

  // --- Find All Audits ---
  async findAll(filters?: { companyId?: number; status?: string }) {
    const query = this.auditRepository
      .createQueryBuilder('audit')
      .leftJoinAndSelect('audit.company', 'company')
      .leftJoinAndSelect('audit.auditType', 'auditType')
      .leftJoinAndSelect('audit.standard', 'standard')
      .leftJoinAndSelect('audit.standards', 'standards')
      // ✅ NEW — load stages and their auditor
      .leftJoinAndSelect('audit.stages', 'stages')
      .leftJoinAndSelect('stages.auditBy', 'stageAuditBy');

    if (filters?.companyId)
      query.andWhere('company.id = :companyId', {
        companyId: filters.companyId,
      });
    if (filters?.status)
      query.andWhere('audit.status = :status', { status: filters.status });

    return query
      .orderBy('audit.createdAt', 'DESC')
      .addOrderBy('stages.auditDate', 'DESC')
      .getMany();
  }

  async findOne(id: number) {
    const audit = await this.auditRepository.findOne({
      where: { id },
      relations: [
        'company',
        'auditType',
        'standard',
        'standards',
        'stages',
        'stages.auditBy',
      ],
    });
    if (!audit) throw new NotFoundException('Audit not found');
    return audit;
  }

  // --- Update Audit (single or multiple standards) ---
  async update(
    id: number,
    dto: UpdateCompanyAuditDto,
  ): Promise<CompanyAudit[]> {
    return await this.dataSource.transaction(async (manager) => {
      const audit = await manager.findOne(CompanyAudit, {
        where: { id },
        relations: ['company', 'auditType', 'standard', 'standards'],
      });
      if (!audit) throw new NotFoundException('Audit not found');

      if (dto.companyId) {
        const company = await manager.findOne(Company, {
          where: { id: dto.companyId },
        });
        if (!company) throw new NotFoundException('Company not found');
        audit.company = company;
      }

      if (dto.auditTypeId) {
        const type = await manager.findOne(AuditType, {
          where: { id: dto.auditTypeId },
        });
        if (!type) throw new NotFoundException('Audit type not found');
        audit.auditType = type;
      }

      // Update standards (single or multiple)
      if (dto.standardIds && dto.standardIds.length > 0) {
        const standards = await manager.findByIds(Standard, dto.standardIds);
        if (standards.length !== dto.standardIds.length)
          throw new NotFoundException('One or more standards not found');

        audit.standards = standards; // attach multiple standards
        audit.standard = standards[0]; // backward compatibility
      } else if (dto.standardId) {
        const standard = await manager.findOne(Standard, {
          where: { id: dto.standardId },
        });
        if (!standard) throw new NotFoundException('Standard not found');

        audit.standards = [standard];
        audit.standard = standard;
      }

      if (dto.auditMode !== undefined) audit.auditMode = dto.auditMode;
      if (dto.validityPeriod !== undefined)
        audit.validityPeriod = dto.validityPeriod;
      if (dto.currentStage !== undefined) audit.currentStage = dto.currentStage;
      if (dto.remarks !== undefined) audit.remarks = dto.remarks;

      const savedAudit = await manager.save(audit);
      return [savedAudit]; // always return array
    });
  }

  // --- Find By Company ID ---
  async findByCompanyId(companyId: number) {
    return this.auditRepository.find({
      where: { company: { id: companyId } },
      relations: ['company', 'auditType', 'standard'],
      order: { createdAt: 'DESC' }, // newest first
    });
  }

  // --- Remove Audit ---
  async remove(id: number) {
    const audit = await this.auditRepository.findOne({ where: { id } });
    if (!audit) throw new NotFoundException('Audit not found');
    return this.auditRepository.remove(audit);
  }
}
