import prisma from '../../config/database';
import { AppError } from '../../shared/middleware/errorHandler';
import { AuditLogger } from '../../shared/utils/auditLogger';
import { Prisma } from '@prisma/client';
import path from 'path';
  import fs from 'fs'
  import { config } from '../../config';


const TENDER_SOURCE_VALUES = [
  'GOVERNMENT_SITE',
  'OFFICIAL_GAZETTE',
  'PERSONAL',
  'PARTNER',
  'WHATSAPP_TELEGRAM',
  'PORTAL',
  'EMAIL',
  'MANUAL',
] as const;

const ANNOUNCEMENT_TYPE_VALUES = [
  'FIRST',
  'RE_ANNOUNCEMENT_2',
  'RE_ANNOUNCEMENT_3',
  'RE_ANNOUNCEMENT_4',
] as const;

const DIRECTIVE_TYPE_VALUES = [
  'BUY_TERMS',
  'VISIT_CLIENT',
  'MEET_COMMITTEE',
  'PREPARE_TO_BID',
] as const;

export interface CreateTenderData {
  issuingBodyName: string;
  title: string;
  tenderNumber?: string;
  issueNumber?: string;

  termsValue: number;
  bondValue: number;

  // new extra fields stored inside notes metadata
  initialBondValue?: number;
  finalBondValue?: number;
  finalBondRefundPeriod?: string;
  siteVisitRequired?: boolean;
  siteVisitLocation?: string;
  termsPickupProvince?: string;

  announcementDate?: string;
  closingDate?: string;
  announcementLink?: string;

  source: string;
  sourceOther?: string;
  announcementType: string;

  notes?: string;
  contactId?: string;
}


export interface CreateDirectiveData {
  type: string;
  notes?: string;
  assignedToEmployeeId: string;
}

export interface TenderWithDuplicates {
  tender: any;
  possibleDuplicates?: any[];
}

class TendersService {
  async generateTenderNumber(): Promise<string> {
    const year = new Date().getFullYear();
    const count = await prisma.tender.count({
      where: {
        createdAt: {
          gte: new Date(`${year}-01-01`),
          lt: new Date(`${year + 1}-01-01`),
        },
      },
    });
    const seq = String(count + 1).padStart(5, '0');
    return `TND-${year}-${seq}`;
  }

  private readonly EXTRA_META_START = '[EXTRA_TENDER_META]';
  private readonly EXTRA_META_END = '[/EXTRA_TENDER_META]';
  private getCompanyTodayDate(): Date {
  const parts = new Intl.DateTimeFormat('en-US', {
    timeZone: 'Asia/Riyadh',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  }).formatToParts(new Date());

  const year = parts.find((p) => p.type === 'year')?.value;
  const month = parts.find((p) => p.type === 'month')?.value;
  const day = parts.find((p) => p.type === 'day')?.value;

  return new Date(`${year}-${month}-${day}T00:00:00.000Z`);
}

private toDateOnly(value: Date | string | null | undefined): Date | null {
  if (!value) return null;

  const date = value instanceof Date ? value : new Date(value);

  if (Number.isNaN(date.getTime())) return null;

  const year = date.getUTCFullYear();
  const month = String(date.getUTCMonth() + 1).padStart(2, '0');
  const day = String(date.getUTCDate()).padStart(2, '0');

  return new Date(`${year}-${month}-${day}T00:00:00.000Z`);
}

private getEffectiveTenderStatus(tender: {
  status?: string | null;
  closingDate?: Date | string | null;
}) {
  if (tender.status === 'ACTIVE') {
    const closingDate = this.toDateOnly(tender.closingDate);
    const today = this.getCompanyTodayDate();

    if (closingDate && closingDate < today) {
      return 'EXPIRED';
    }
  }

  return tender.status || 'ACTIVE';
}

  private extractTenderExtraMeta(notes?: string | null) {
    if (!notes) {
      return {
        cleanNotes: '',
        meta: {},
      };
    }

    const start = notes.indexOf(this.EXTRA_META_START);
    const end = notes.indexOf(this.EXTRA_META_END);

    if (start === -1 || end === -1 || end < start) {
      return {
        cleanNotes: notes,
        meta: {},
      };
    }

    const jsonPart = notes.slice(start + this.EXTRA_META_START.length, end).trim();
    const before = notes.slice(0, start).trim();
    const after = notes.slice(end + this.EXTRA_META_END.length).trim();
    const cleanNotes = [before, after].filter(Boolean).join('\n').trim();

    try {
      return {
        cleanNotes,
        meta: JSON.parse(jsonPart || '{}'),
      };
    } catch {
      return {
        cleanNotes: notes,
        meta: {},
      };
    }
  }

  async delete(id: string, userId: string) {
    const tender = await prisma.tender.findUnique({
      where: { id },
      include: {
        attachments: true,
        directives: {
          include: {
            attachments: true,
          },
        },
        convertedDeal: {
          select: { id: true },
        },
      },
    });

    if (!tender) {
      throw new AppError(404, 'Tender not found');
    }

    if (tender.convertedDeal) {
      throw new AppError(400, 'Cannot delete tender that has been converted to deal');
    }

    for (const attachment of tender.attachments || []) {
      if (attachment.path && fs.existsSync(attachment.path)) {
        fs.unlinkSync(attachment.path);
      }
    }

    for (const directive of tender.directives || []) {
      for (const attachment of directive.attachments || []) {
        if (attachment.path && fs.existsSync(attachment.path)) {
          fs.unlinkSync(attachment.path);
        }
      }
    }

    await prisma.tender.delete({
      where: { id },
    });

    await AuditLogger.log({
      entityType: 'TENDER',
      entityId: id,
      action: 'DELETE',
      userId,
      changes: {
        deletedTenderNumber: tender.tenderNumber,
        deletedTitle: tender.title,
      },
    });

    return true;
  }

  private buildTenderNotes(
    plainNotes?: string | null,
    extra?: {
      initialBondValue?: number | null;
      finalBondValue?: number | null;
      finalBondRefundPeriod?: string | null;
      siteVisitRequired?: boolean;
      siteVisitLocation?: string | null;
      termsPickupProvince?: string | null;
    }
  ) {
    const cleanedNotes = plainNotes?.trim() || '';

    const meta = {
      initialBondValue: extra?.initialBondValue ?? null,
      finalBondValue: extra?.finalBondValue ?? null,
      finalBondRefundPeriod: extra?.finalBondRefundPeriod?.trim() || null,
      siteVisitRequired: !!extra?.siteVisitRequired,
      siteVisitLocation: extra?.siteVisitLocation?.trim() || null,
      termsPickupProvince: extra?.termsPickupProvince?.trim() || null,
    };

    const metaBlock = `${this.EXTRA_META_START}${JSON.stringify(meta)}${this.EXTRA_META_END}`;

    return cleanedNotes ? `${cleanedNotes}\n${metaBlock}` : metaBlock;
  }

    private mapTenderExtraFields<T extends {
      notes?: string | null;
      bondValue?: any;
      status?: string | null;
      closingDate?: Date | string | null;
    }>(tender: T) {
      const { cleanNotes, meta } = this.extractTenderExtraMeta(tender.notes);

    return {
      ...tender,
      status: this.getEffectiveTenderStatus(tender),
      notes: cleanNotes || null,
      initialBondValue: meta.initialBondValue ?? Number(tender.bondValue ?? 0),
      finalBondValue: meta.finalBondValue ?? null,
      finalBondRefundPeriod: meta.finalBondRefundPeriod ?? null,
      siteVisitRequired: !!meta.siteVisitRequired,
      siteVisitLocation: meta.siteVisitLocation ?? null,
      termsPickupProvince: meta.termsPickupProvince ?? null,
    };
  }

  async findPossibleDuplicates(data: CreateTenderData): Promise<any[]> {
    const announcementDate = data.announcementDate ? new Date(data.announcementDate) : null;
    const closingDate = data.closingDate ? new Date(data.closingDate) : null;
    const termsValue = Number(data.termsValue);
    const bondValue = Number(data.initialBondValue ?? data.bondValue ?? 0);
    const where: Prisma.TenderWhereInput = {
      status: { not: 'CANCELLED' },
    };

    const orConditions: Prisma.TenderWhereInput[] = [];

    if (data.issuingBodyName?.trim()) {
      orConditions.push({
        issuingBodyName: { contains: data.issuingBodyName.trim(), mode: 'insensitive' },
      });
    }
    if (data.title?.trim()) {
      orConditions.push({
        title: { contains: data.title.trim(), mode: 'insensitive' },
      });
    }
    if (orConditions.length) {
      where.OR = orConditions;
    }

    if (announcementDate) {
      where.announcementDate = announcementDate;
    }
    if (closingDate) {
      where.closingDate = closingDate;
    }
    if (termsValue != null && !isNaN(termsValue)) {
      where.termsValue = termsValue;
    }
    if (bondValue != null && !isNaN(bondValue)) {
      where.bondValue = bondValue;
    }

    const tenders = await prisma.tender.findMany({
      where,
      take: 10,
      include: {
        createdBy: { select: { id: true, email: true, username: true } },
      },
      orderBy: { createdAt: 'desc' },
    });
    return tenders;
  }

  async create(data: CreateTenderData, userId: string): Promise<TenderWithDuplicates> {
    const possibleDuplicates = await this.findPossibleDuplicates(data);

    // رقم المناقصة اختياري في الواجهة، لكن العمود في قاعدة البيانات مطلوب وفريد.
    // إذا تُرك فاضياً نولّد رقماً تلقائياً حتى لا يفشل الحفظ ولا نحتاج migration.
    let tenderNumber = data.tenderNumber?.trim() || '';
    if (tenderNumber) {
      const existing = await prisma.tender.findUnique({
        where: { tenderNumber },
      });
      if (existing) {
        throw new AppError(400, 'Tender number already exists - رقم المناقصة موجود مسبقاً');
      }
    } else {
      // توليد رقم تلقائي فريد مع تفادي التعارض النادر
      tenderNumber = await this.generateTenderNumber();
      let guard = 0;
      while (await prisma.tender.findUnique({ where: { tenderNumber } })) {
        tenderNumber = `${await this.generateTenderNumber()}-${++guard}`;
        if (guard > 5) {
          tenderNumber = `TND-${Date.now()}`;
          break;
        }
      }
    }

    // تاريخ الإعلان/الإغلاق اختياريان في الواجهة، والعمودان مطلوبان في قاعدة البيانات.
    // إذا تُركا فاضيين نستخدم تاريخ اليوم افتراضياً.
    const announcementDate = data.announcementDate ? new Date(data.announcementDate) : new Date();
    const closingDate = data.closingDate ? new Date(data.closingDate) : new Date();

    if (data.siteVisitRequired && !data.siteVisitLocation?.trim()) {
      throw new AppError(400, 'مكان زيارة الموقع مطلوب عند اختيار زيارة موقع إجبارية');
    }

    const finalNotes = this.buildTenderNotes(data.notes, {
      initialBondValue: data.initialBondValue ?? data.bondValue ?? 0,
      finalBondValue: data.finalBondValue ?? null,
      finalBondRefundPeriod: data.finalBondRefundPeriod ?? null,
      siteVisitRequired: !!data.siteVisitRequired,
      siteVisitLocation: data.siteVisitRequired ? data.siteVisitLocation ?? null : null,
      termsPickupProvince: data.termsPickupProvince ?? null,
    });

    const tender = await prisma.tender.create({
      data: {
        tenderNumber,
        issueNumber: data.issueNumber?.trim() || null,
        issuingBodyName: data.issuingBodyName.trim(),
        title: data.title.trim(),
        termsValue: Number(data.termsValue ?? 0),
        bondValue: Number(data.initialBondValue ?? data.bondValue ?? 0),
        announcementDate,
        closingDate,
        announcementLink: data.announcementLink?.trim() || null,
        source: data.source,
        sourceOther: data.sourceOther?.trim() || null,
        announcementType: data.announcementType,
        notes: finalNotes,
        contactId: data.contactId || null,
        createdById: userId,
      },
      include: {
        createdBy: { select: { id: true, email: true, username: true } },
        contact: { select: { id: true, name: true, email: true } },
      },
    });

    await AuditLogger.log({
      entityType: 'TENDER',
      entityId: tender.id,
      action: 'CREATE',
      userId,
    });

    return {
      tender: this.mapTenderExtraFields(tender),
      possibleDuplicates: possibleDuplicates.length ? possibleDuplicates.map((t) => this.mapTenderExtraFields(t)) : undefined,
    };  }

  async findAll(filters: any, page: number, pageSize: number) {
    const skip = (page - 1) * pageSize;
    const where: Prisma.TenderWhereInput = {};

    if (filters.search) {
      where.OR = [
        { tenderNumber: { contains: filters.search, mode: 'insensitive' } },
        { issueNumber: { contains: filters.search, mode: 'insensitive' } },
        { title: { contains: filters.search, mode: 'insensitive' } },
        { issuingBodyName: { contains: filters.search, mode: 'insensitive' } },
      ];
    }
    if (filters.status === 'EXPIRED') {
      where.status = 'ACTIVE';
      where.closingDate = { lt: this.getCompanyTodayDate() };
    } else if (filters.status === 'ACTIVE') {
      where.status = 'ACTIVE';
      where.closingDate = { gte: this.getCompanyTodayDate() };
    } else if (filters.status) {
      where.status = filters.status;
    }
    if (filters.source) where.source = filters.source;
    if (filters.announcementType) where.announcementType = filters.announcementType;

    const total = await prisma.tender.count({ where });
    const tenders = await prisma.tender.findMany({
      where,
      skip,
      take: pageSize,
      include: {
        createdBy: { select: { id: true, email: true, username: true } },
        contact: { select: { id: true, name: true } },
        _count: { select: { directives: true } },
      },
      orderBy: { createdAt: 'desc' },
    });
        return {
      tenders: tenders.map((t) => this.mapTenderExtraFields(t)),
      total,
      page,
      pageSize,
    };
  }

  async findById(id: string) {
    const tender = await prisma.tender.findUnique({
      where: { id },
      include: {
        createdBy: { select: { id: true, email: true, username: true, employee: { select: { firstName: true, lastName: true } } } },
        contact: true,
        directives: {
          include: {
            assignedToEmployee: { select: { id: true, firstName: true, lastName: true, email: true, user: { select: { id: true } } } },
            issuedBy: { select: { id: true, email: true, username: true } },
            completedBy: { select: { id: true, email: true } },
            attachments: true,
          },
          orderBy: { createdAt: 'desc' },
        },
        attachments: true,
      },
    });
        if (!tender) throw new AppError(404, 'Tender not found');
       return this.mapTenderExtraFields(tender);
  }

  async update(id: string, data: Partial<CreateTenderData>, userId: string) {
    const existing = await prisma.tender.findUnique({ where: { id } });
    if (!existing) throw new AppError(404, 'Tender not found');
    if (existing.status === 'CONVERTED_TO_DEAL') {
      throw new AppError(400, 'Cannot update tender that has been converted to deal');
    }

    const updateData: Prisma.TenderUpdateInput = {};
        const existingMapped = this.mapTenderExtraFields(existing as any);

    const mergedExtra = {
      initialBondValue:
        data.initialBondValue !== undefined
          ? Number(data.initialBondValue)
          : existingMapped.initialBondValue ?? Number(existing.bondValue ?? 0),

      finalBondValue:
        data.finalBondValue !== undefined
          ? Number(data.finalBondValue)
          : existingMapped.finalBondValue ?? null,

      finalBondRefundPeriod:
        data.finalBondRefundPeriod !== undefined
          ? data.finalBondRefundPeriod
          : existingMapped.finalBondRefundPeriod ?? null,

      siteVisitRequired:
        data.siteVisitRequired !== undefined
          ? !!data.siteVisitRequired
          : !!existingMapped.siteVisitRequired,

      siteVisitLocation:
        data.siteVisitLocation !== undefined
          ? data.siteVisitLocation
          : existingMapped.siteVisitLocation ?? null,

      termsPickupProvince:
        data.termsPickupProvince !== undefined
          ? data.termsPickupProvince
          : existingMapped.termsPickupProvince ?? null,
    };

    if (mergedExtra.siteVisitRequired && !mergedExtra.siteVisitLocation?.trim()) {
      throw new AppError(400, 'مكان زيارة الموقع مطلوب عند اختيار زيارة موقع إجبارية');
    }
    if (data.title !== undefined) updateData.title = data.title.trim();
    if (data.issueNumber !== undefined) {
      updateData.issueNumber = data.issueNumber?.trim() || null;
    }
    if (data.issuingBodyName !== undefined) updateData.issuingBodyName = data.issuingBodyName.trim();
    if (data.termsValue !== undefined) updateData.termsValue = data.termsValue;
    if (data.bondValue !== undefined || data.initialBondValue !== undefined) {
      updateData.bondValue = Number(data.initialBondValue ?? data.bondValue ?? existing.bondValue);
    }
    // العمود مطلوب في قاعدة البيانات: نتجاهل القيمة الفاضية ونبقي على القيمة الحالية بدل كتابة null.
    if (data.tenderNumber !== undefined && data.tenderNumber?.trim()) {
      updateData.tenderNumber = data.tenderNumber.trim();
    }
    if (data.announcementDate !== undefined && data.announcementDate) {
      updateData.announcementDate = new Date(data.announcementDate);
    }
    if (data.closingDate !== undefined && data.closingDate) {
      updateData.closingDate = new Date(data.closingDate);
    }
    if (data.announcementLink !== undefined) updateData.announcementLink = data.announcementLink?.trim() || null;
    if (data.source !== undefined) updateData.source = data.source;
    if (data.sourceOther !== undefined) updateData.sourceOther = data.sourceOther?.trim() || null;
    if (data.announcementType !== undefined) updateData.announcementType = data.announcementType;
    if (
      data.notes !== undefined ||
      data.initialBondValue !== undefined ||
      data.finalBondValue !== undefined ||
      data.finalBondRefundPeriod !== undefined ||
      data.siteVisitRequired !== undefined ||
      data.siteVisitLocation !== undefined ||
      data.termsPickupProvince !== undefined
    ) {
      updateData.notes = this.buildTenderNotes(
        data.notes !== undefined ? data.notes : existingMapped.notes,
        {
          initialBondValue: mergedExtra.initialBondValue,
          finalBondValue: mergedExtra.finalBondValue,
          finalBondRefundPeriod: mergedExtra.finalBondRefundPeriod,
          siteVisitRequired: mergedExtra.siteVisitRequired,
          siteVisitLocation: mergedExtra.siteVisitRequired ? mergedExtra.siteVisitLocation : null,
          termsPickupProvince: mergedExtra.termsPickupProvince,
        }
      );
    }
    if (data.contactId !== undefined) {
      updateData.contact = data.contactId
        ? { connect: { id: data.contactId } }
        : { disconnect: true };
    }

    const tender = await prisma.tender.update({
      where: { id },
      data: updateData,
      include: {
        createdBy: { select: { id: true, email: true, username: true } },
        contact: { select: { id: true, name: true } },
      },
    });

    await AuditLogger.log({
      entityType: 'TENDER',
      entityId: id,
      action: 'UPDATE',
      userId,
      changes: { before: existing, after: data },
    });
    return this.mapTenderExtraFields(tender);
    }

  async createDirective(tenderId: string, data: CreateDirectiveData, userId: string) {
    const tender = await prisma.tender.findUnique({
      where: { id: tenderId },
      select: { id: true, title: true, tenderNumber: true },
    });
    if (!tender) throw new AppError(404, 'Tender not found');

    const directive = await prisma.tenderDirective.create({
      data: {
        tenderId,
        type: data.type,
        notes: data.notes?.trim() || null,
        assignedToEmployeeId: data.assignedToEmployeeId,
        issuedById: userId,
      },
      include: {
        assignedToEmployee: {
          select: { id: true, firstName: true, lastName: true, user: { select: { id: true } } },
        },
        issuedBy: { select: { id: true, email: true, username: true } },
      },
    });

    const assignedUser = directive.assignedToEmployee?.user;
    if (assignedUser?.id) {
      const typeLabel = this.getDirectiveTypeLabel(data.type);
      await prisma.notification.create({
        data: {
          userId: assignedUser.id,
          type: 'TENDER_DIRECTIVE_ASSIGNED',
          title: `مهمة مناقصة - Tender task: ${tender.tenderNumber}`,
          message: `${tender.title} (${tender.tenderNumber}): ${typeLabel}. ${data.notes || ''}`,
          entityType: 'TENDER_DIRECTIVE',
          entityId: directive.id,
        },
      });
    }

    await AuditLogger.log({
      entityType: 'TENDER_DIRECTIVE',
      entityId: directive.id,
      action: 'CREATE',
      userId,
    });

    return directive;
  }

  async updateDirective(
    directiveId: string,
    data: { status?: string; completionNotes?: string },
    userId: string
  ) {
    const directive = await prisma.tenderDirective.findUnique({
      where: { id: directiveId },
      include: { tender: true },
    });
    if (!directive) throw new AppError(404, 'Directive not found');

    const updateData: Prisma.TenderDirectiveUpdateInput = {};
    if (data.status !== undefined) updateData.status = data.status;
    if (data.completionNotes !== undefined) updateData.completionNotes = data.completionNotes;
    if (data.status === 'COMPLETED') {
      updateData.completedAt = new Date();
      updateData.completedBy = { connect: { id: userId } };
    }

    const updated = await prisma.tenderDirective.update({
      where: { id: directiveId },
      data: updateData,
      include: {
        assignedToEmployee: { select: { id: true, firstName: true, lastName: true } },
        issuedBy: { select: { id: true, email: true } },
      },
    });

    await AuditLogger.log({
      entityType: 'TENDER_DIRECTIVE',
      entityId: directiveId,
      action: 'UPDATE',
      userId,
    });
    return updated;
  }

  async getHistory(tenderId: string) {
    const tender = await prisma.tender.findUnique({ where: { id: tenderId } });
    if (!tender) throw new AppError(404, 'Tender not found');
    return AuditLogger.getEntityHistory('TENDER', tenderId);
  }

  async getDirectiveHistory(directiveId: string) {
    const dir = await prisma.tenderDirective.findUnique({ where: { id: directiveId } });
    if (!dir) throw new AppError(404, 'Directive not found');
    return AuditLogger.getEntityHistory('TENDER_DIRECTIVE', directiveId);
  }

  getDirectiveTypeLabel(type: string): string {
    const labels: Record<string, string> = {
      BUY_TERMS: 'شراء دفتر الشروط - Buy terms booklet',
      VISIT_CLIENT: 'زيارة الزبون - Visit client',
      MEET_COMMITTEE: 'التعرف على اللجنة المختصة - Meet committee',
      PREPARE_TO_BID: 'الاستعداد للدخول في المناقصة - Prepare to bid',
    };
    return labels[type] || type;
  }

  getSourceValues() {
    return [...TENDER_SOURCE_VALUES];
  }
  getAnnouncementTypeValues() {
    return [...ANNOUNCEMENT_TYPE_VALUES];
  }
  getDirectiveTypeValues() {
    return [...DIRECTIVE_TYPE_VALUES];
  }

  /**
   * Returns a minimal employee list (id + names only) for filling the
   * directive assignee dropdown. Intentionally does NOT include salary,
   * national ID, passport, email, phone, or any other sensitive HR fields
   * so this endpoint can be exposed to anyone with directive-create
   * permission without leaking HR data.
   */
  async getAssignableEmployees() {
    return prisma.employee.findMany({
      where: { status: 'ACTIVE' },
      select: {
        id: true,
        firstName: true,
        lastName: true,
        firstNameAr: true,
        lastNameAr: true,
      },
      orderBy: [{ firstName: 'asc' }, { lastName: 'asc' }],
    });
  }

  async convertToDeal(
    tenderId: string,
    data: { contactId: string; pipelineId: string; ownerId?: string },
    userId: string
  ) {
    const tender = await prisma.tender.findUnique({
      where: { id: tenderId },
      include: { contact: true },
    });
    if (!tender) throw new AppError(404, 'Tender not found');
    if (tender.status === 'CONVERTED_TO_DEAL') {
      throw new AppError(400, 'Tender already converted to deal');
    }
    if (this.getEffectiveTenderStatus(tender) === 'EXPIRED') {
      throw new AppError(400, 'Cannot convert expired tender to deal');
    }

    const pipeline = await prisma.pipeline.findUnique({
      where: { id: data.pipelineId },
    });
    if (!pipeline) throw new AppError(404, 'Pipeline not found');
    const stages = (pipeline.stages as { id?: string; name?: string }[]) || [];
    const firstStage = stages[0]?.id || stages[0]?.name || 'OPEN';

    const dealNumber = await this.generateDealNumber();
    const fiscalYear = new Date().getFullYear();
    const estimatedValue = Number(tender.termsValue) || Number(tender.bondValue) || 0;

    const deal = await prisma.deal.create({
      data: {
        dealNumber,
        name: tender.title,
        contactId: data.contactId,
        structure: 'B2G',
        pipelineId: data.pipelineId,
        stage: firstStage,
        estimatedValue,
        ownerId: data.ownerId || userId,
        fiscalYear,
        currency: 'SAR',
        sourceTenderId: tenderId,
      },
      include: {
        contact: { select: { id: true, name: true, email: true } },
        owner: { select: { id: true, email: true, username: true } },
        pipeline: true,
      },
    });

    await prisma.tender.update({
      where: { id: tenderId },
      data: { status: 'CONVERTED_TO_DEAL' },
    });

    await AuditLogger.log({
      entityType: 'TENDER',
      entityId: tenderId,
      action: 'UPDATE',
      userId,
      changes: { status: 'CONVERTED_TO_DEAL', dealId: deal.id },
    });
    await AuditLogger.log({
      entityType: 'DEAL',
      entityId: deal.id,
      action: 'CREATE',
      userId,
    });
    return deal;
  }

  async uploadTenderAttachment(
    tenderId: string,
    file: { path: string; originalname: string; mimetype: string; size: number },
    userId: string,
    category?: string
  ) {
    const tender = await prisma.tender.findUnique({ where: { id: tenderId } });
    if (!tender) throw new AppError(404, 'Tender not found');

    const absolutePath = path.resolve(file.path);
    const fileName = path.basename(absolutePath);

    // Verify multer actually wrote the file to disk before recording it.
    if (!fs.existsSync(absolutePath)) {
      console.error('[tenders.uploadTenderAttachment] Multer reported a file but it does not exist on disk', {
        tenderId,
        multerPath: file.path,
        resolvedPath: absolutePath,
        size: file.size,
      });
      throw new AppError(500, 'File upload failed - فشل رفع الملف');
    }

    console.log('[tenders.uploadTenderAttachment] File saved', {
      tenderId,
      path: absolutePath,
      size: file.size,
    });

    const attachment = await prisma.attachment.create({
      data: {
        entityType: 'TENDER',
        entityId: tenderId,
        tenderId,
        fileName,
        originalName: file.originalname,
        mimeType: file.mimetype,
        size: file.size,
        path: absolutePath,
        category: category || 'ANNOUNCEMENT',
        uploadedBy: userId,
      },
    });

    await AuditLogger.log({
      entityType: 'TENDER',
      entityId: tenderId,
      action: 'UPDATE',
      userId,
      changes: { attachmentUploaded: attachment.id },
    });

    return attachment;
  }

  async uploadDirectiveAttachment(
    directiveId: string,
    file: { path: string; originalname: string; mimetype: string; size: number },
    userId: string,
    category?: string
  ) {
    const directive = await prisma.tenderDirective.findUnique({
      where: { id: directiveId },
      select: { id: true, tenderId: true },
    });

    if (!directive) throw new AppError(404, 'Directive not found');

    const absolutePath = path.resolve(file.path);
    const fileName = path.basename(absolutePath);

    // Verify multer actually wrote the file to disk before recording it.
    if (!fs.existsSync(absolutePath)) {
      console.error('[tenders.uploadDirectiveAttachment] Multer reported a file but it does not exist on disk', {
        directiveId,
        multerPath: file.path,
        resolvedPath: absolutePath,
        size: file.size,
      });
      throw new AppError(500, 'File upload failed - فشل رفع الملف');
    }

    console.log('[tenders.uploadDirectiveAttachment] File saved', {
      directiveId,
      path: absolutePath,
      size: file.size,
    });

    const attachment = await prisma.attachment.create({
      data: {
        entityType: 'TENDER_DIRECTIVE',
        entityId: directiveId,
        tenderDirectiveId: directiveId,
        tenderId: directive.tenderId,
        fileName,
        originalName: file.originalname,
        mimeType: file.mimetype,
        size: file.size,
        path: absolutePath,
        category: category || 'TASK_FILE',
        uploadedBy: userId,
      },
    });

    await AuditLogger.log({
      entityType: 'TENDER_DIRECTIVE',
      entityId: directiveId,
      action: 'UPDATE',
      userId,
      changes: { attachmentUploaded: attachment.id },
    });

    return attachment;
  }

  async getAttachmentFile(attachmentId: string): Promise<string> {
    const attachment = await prisma.attachment.findUnique({
      where: { id: attachmentId },
    });

    if (!attachment) throw new AppError(404, 'File not found');

    // Try multiple candidate locations for the file (in order of preference).
    // This makes the system resilient to path changes between deploys (e.g.
    // when an old DB row has a stale absolute path).
    const candidates = [
      attachment.path,
      path.join(config.upload.path, 'tenders', attachment.fileName),
      path.join(config.upload.path, attachment.fileName),
      path.join(process.cwd(), 'uploads', 'tenders', attachment.fileName),
    ]
      .filter(Boolean)
      .map((p) => path.resolve(String(p)));

    const existingPath = candidates.find((p) => fs.existsSync(p));

    if (!existingPath) {
      console.error('[tenders.getAttachmentFile] File not found on disk', {
        attachmentId,
        storedPath: attachment.path,
        fileName: attachment.fileName,
        uploadConfigPath: config.upload.path,
        triedCandidates: candidates,
      });
      throw new AppError(404, 'File not found - الملف غير موجود');
    }

    // Self-healing: if the file lives at a path other than what's stored,
    // update the DB so future lookups are direct.
    if (existingPath !== attachment.path) {
      console.warn('[tenders.getAttachmentFile] Stored path was stale, updating', {
        attachmentId,
        oldPath: attachment.path,
        newPath: existingPath,
      });
      try {
        await prisma.attachment.update({
          where: { id: attachmentId },
          data: { path: existingPath },
        });
      } catch (err) {
        // Non-fatal: we still have the resolved path to serve from.
        console.error('[tenders.getAttachmentFile] Failed to update stale path', err);
      }
    }

    return existingPath;
  }

  async deleteAttachment(attachmentId: string): Promise<void> {
    const attachment = await prisma.attachment.findUnique({
      where: { id: attachmentId },
    })

    if (!attachment) throw new AppError(404, 'File not found')

    if (attachment.path && fs.existsSync(attachment.path)) {
      fs.unlinkSync(attachment.path)
    }

    await prisma.attachment.delete({
      where: { id: attachmentId },
    })
  }

  private async generateDealNumber(): Promise<string> {
    const year = new Date().getFullYear();
    const prefix = `DEAL-${year}-`;
    const lastDeal = await prisma.deal.findFirst({
      where: { dealNumber: { startsWith: prefix } },
      orderBy: { createdAt: 'desc' },
      select: { dealNumber: true },
    });
    let nextNumber = 1;
    if (lastDeal) {
      const part = lastDeal.dealNumber.split('-')[2];
      nextNumber = (parseInt(part, 10) || 0) + 1;
    }
    return `${prefix}${nextNumber.toString().padStart(6, '0')}`;
  }
}

export const tendersService = new TendersService();
