#!/usr/bin/env python3
"""
Run this script on the server to add slot system code to audit-requests.service.ts
Usage: python3 add_slots.py
"""
import re

FILE = '/var/www/scheme_certiifcation/backend/src/audit-requests/services/audit-requests.service.ts'

# Read original
with open(FILE, 'r') as f:
    content = f.read()

# Backup
with open(FILE + '.bak', 'w') as f:
    f.write(content)
print(f"Backup saved to {FILE}.bak")

# 1. Add constants after EXTRA_ADMIN_EMAILS line
old_const = "const EXTRA_ADMIN_EMAILS = ['account.ehs@qrs.ae'];"
new_const = old_const + """

// SLOT SYSTEM CONFIG
const SUBMISSION_WINDOW_DAYS = 2;
const SLOT_GAP_DAYS = 7;"""

if 'SLOT_GAP_DAYS' not in content:
    content = content.replace(old_const, new_const)
    print("Added SLOT constants")
else:
    print("SLOT constants already present")

# 2. Add slot validation call in create() — before "const saved = await manager.save(request);" in the create method
# Find the first occurrence of this pattern after "async create("
create_marker = "const saved = await manager.save(request);"
# We need to find the FIRST occurrence (in create method, not createBatch)
create_pos = content.find("async create(")
if create_pos >= 0:
    save_pos = content.find(create_marker, create_pos)
    if save_pos >= 0 and 'validateSlotAvailability' not in content[create_pos:save_pos]:
        slot_check = """
      // SLOT SYSTEM — validate submission window + gap rule
      await this.validateSlotAvailability(dto.proposed_date, manager);

      """
        content = content[:save_pos] + slot_check + content[save_pos:]
        print("Added slot validation to create()")
    else:
        print("Slot validation already in create() or marker not found")

# 3. Add slot validation call in createBatch() — before "const saved = await manager.save(request);" in batch
batch_marker = "async createBatch("
batch_pos = content.find(batch_marker)
if batch_pos >= 0:
    # Find "const saved = await manager.save(request);" AFTER createBatch
    batch_save_pos = content.find(create_marker, batch_pos)
    if batch_save_pos >= 0 and 'validateSlotAvailability' not in content[batch_pos:batch_save_pos]:
        slot_check_batch = """
        // SLOT SYSTEM — validate submission window + gap rule
        await this.validateSlotAvailability(c.proposed_date, manager);

        """
        content = content[:batch_save_pos] + slot_check_batch + content[batch_save_pos:]
        print("Added slot validation to createBatch()")
    else:
        print("Slot validation already in createBatch() or marker not found")

# 4. Add getAvailableSlots + validateSlotAvailability + formatDateStr methods
# Insert before the last closing brace of the class
if 'getAvailableSlots' not in content:
    slot_methods = """
  // ═══════════════════════════════════════════════════════════════════════
  // SLOT SYSTEM — submission window + gap enforcement
  // ═══════════════════════════════════════════════════════════════════════

  private async validateSlotAvailability(
    proposedDate: string,
    manager: EntityManager,
    excludeRequestId?: number,
  ): Promise<void> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const proposed = new Date(proposedDate + 'T00:00:00');

    const windowEnd = new Date(today);
    windowEnd.setDate(windowEnd.getDate() + SUBMISSION_WINDOW_DAYS);

    if (proposed < today) {
      throw new BadRequestException(
        'Cannot submit audit request for a past date (' + proposedDate + '). ' +
        'Allowed window: today to ' + this.formatDateStr(windowEnd) + '.',
      );
    }

    if (proposed > windowEnd) {
      throw new BadRequestException(
        'Proposed date ' + proposedDate + ' is outside the submission window. ' +
        'You can only submit requests for today up to ' + this.formatDateStr(windowEnd) +
        ' (' + SUBMISSION_WINDOW_DAYS + '-day window).',
      );
    }

    const gapCheckFrom = new Date(proposed);
    gapCheckFrom.setDate(gapCheckFrom.getDate() - SLOT_GAP_DAYS);
    const gapCheckTo = new Date(proposed);
    gapCheckTo.setDate(gapCheckTo.getDate() + SLOT_GAP_DAYS);

    let qb = manager
      .createQueryBuilder(AuditRequest, 'ar')
      .where('ar.proposed_date BETWEEN :from AND :to', {
        from: this.formatDateStr(gapCheckFrom),
        to: this.formatDateStr(gapCheckTo),
      })
      .andWhere('ar.status NOT IN (:...excluded)', {
        excluded: [AuditRequestStatus.CANCELLED, AuditRequestStatus.REJECTED],
      });

    if (excludeRequestId) {
      qb = qb.andWhere('ar.id != :excludeId', { excludeId: excludeRequestId });
    }

    const conflicting = await qb.getMany();

    if (conflicting.length > 0) {
      const blocker = conflicting[0];
      const blockerDate = new Date(blocker.proposed_date + 'T00:00:00');
      const nextSlot = new Date(blockerDate.getTime() + SLOT_GAP_DAYS * 86400000);
      throw new BadRequestException(
        'Slot unavailable for ' + proposedDate + '. ' +
        'An existing audit request (ID #' + blocker.id + ', "' + (blocker.company_name || 'Unknown') + '", ' +
        'date: ' + blocker.proposed_date + ') is within the ' + SLOT_GAP_DAYS + '-day gap. ' +
        'The next available slot opens after ' + this.formatDateStr(nextSlot) + '.',
      );
    }

    this.logger.log('[SLOT-CHECK] Date ' + proposedDate + ' is available');
  }

  async getAvailableSlots(): Promise<{
    window_start: string;
    window_end: string;
    available_dates: string[];
    blocked_dates: { date: string; reason: string; blocked_by_request_id: number }[];
  }> {
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    const windowEnd = new Date(today);
    windowEnd.setDate(windowEnd.getDate() + SUBMISSION_WINDOW_DAYS);

    const windowStart = this.formatDateStr(today);
    const windowEndStr = this.formatDateStr(windowEnd);

    const lookbackStart = new Date(today);
    lookbackStart.setDate(lookbackStart.getDate() - SLOT_GAP_DAYS);

    const activeRequests = await this.dataSource
      .getRepository(AuditRequest)
      .createQueryBuilder('ar')
      .where('ar.proposed_date BETWEEN :from AND :to', {
        from: this.formatDateStr(lookbackStart),
        to: windowEndStr,
      })
      .andWhere('ar.status NOT IN (:...excluded)', {
        excluded: [AuditRequestStatus.CANCELLED, AuditRequestStatus.REJECTED],
      })
      .getMany();

    const blockedMap = new Map<string, { reason: string; blocked_by_request_id: number }>();

    for (const req of activeRequests) {
      const reqDate = new Date(req.proposed_date + 'T00:00:00');
      const reqDateStr = this.formatDateStr(reqDate);

      if (reqDateStr >= windowStart && reqDateStr <= windowEndStr) {
        blockedMap.set(reqDateStr, {
          reason: 'Booked: ' + (req.company_name || 'Unknown') + ' (ID #' + req.id + ')',
          blocked_by_request_id: req.id,
        });
      }

      for (let g = 1; g <= SLOT_GAP_DAYS; g++) {
        const gapDate = new Date(reqDate);
        gapDate.setDate(gapDate.getDate() + g);
        const gapStr = this.formatDateStr(gapDate);

        if (gapStr >= windowStart && gapStr <= windowEndStr && !blockedMap.has(gapStr)) {
          blockedMap.set(gapStr, {
            reason: 'Within ' + SLOT_GAP_DAYS + '-day gap of ' + (req.company_name || 'Unknown') + ' (' + req.proposed_date + ')',
            blocked_by_request_id: req.id,
          });
        }
      }
    }

    const available: string[] = [];
    const blocked: { date: string; reason: string; blocked_by_request_id: number }[] = [];

    for (let d = new Date(today); d <= windowEnd; d.setDate(d.getDate() + 1)) {
      const ds = this.formatDateStr(d);
      if (blockedMap.has(ds)) {
        const info = blockedMap.get(ds);
        if (info) {
          blocked.push({ date: ds, reason: info.reason, blocked_by_request_id: info.blocked_by_request_id });
        }
      } else {
        available.push(ds);
      }
    }

    return {
      window_start: windowStart,
      window_end: windowEndStr,
      available_dates: available,
      blocked_dates: blocked,
    };
  }

  private formatDateStr(d: Date): string {
    return d.toISOString().split('T')[0];
  }
"""
    # Insert before the very last closing brace
    last_brace = content.rfind('}')
    if last_brace >= 0:
        content = content[:last_brace] + slot_methods + content[last_brace:]
        print("Added getAvailableSlots + validateSlotAvailability + formatDateStr methods")
else:
    print("getAvailableSlots already present")

# Write
with open(FILE, 'w') as f:
    f.write(content)

print(f"\nDone! File updated. Original backup at {FILE}.bak")
print("Now run: cd /var/www/scheme_certiifcation/backend && npm run build && pm2 restart scheme_c-backend")