import {
  Injectable,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { CrmPrismaService } from '../prisma/crm-prisma.service';
import { CreateDealDto } from './dto/create-deal.dto';
import { UpdateDealDto } from './dto/update-deal.dto';
import { QueryDealsDto } from './dto/query-deals.dto';
import { ForecastQueryDto, ForecastPeriod } from './dto/forecast-query.dto';
import { CrmEventPublisher } from '../events/crm-event-publisher.service';
import { CustomFieldsService } from '../custom-fields/custom-fields.service';
import { CustomFieldEntityType } from '../custom-fields/dto/create-custom-field.dto';
import { Prisma } from '.prisma/crm-client';

@Injectable()
export class DealsService {
  constructor(
    private readonly prisma: CrmPrismaService,
    private readonly eventPublisher: CrmEventPublisher,
    private readonly customFieldsService: CustomFieldsService,
  ) {}

  async create(tenantId: string, userId: string, dto: CreateDealDto) {
    // Pipeline und Stage validieren
    const pipeline = await this.prisma.pipeline.findFirst({
      where: { id: dto.pipelineId, tenantId },
    });
    if (!pipeline) {
      throw new NotFoundException('Pipeline nicht gefunden');
    }

    const stage = await this.prisma.pipelineStage.findFirst({
      where: { id: dto.stageId, pipelineId: dto.pipelineId },
    });
    if (!stage) {
      throw new NotFoundException('Pipeline-Stufe nicht gefunden');
    }

    // Kontakt validieren (optional)
    if (dto.contactId) {
      const contact = await this.prisma.contact.findFirst({
        where: { id: dto.contactId, tenantId },
      });
      if (!contact) {
        throw new NotFoundException('Kontakt nicht gefunden');
      }
    }

    // Unternehmen validieren (optional)
    if (dto.companyId) {
      const company = await this.prisma.company.findFirst({
        where: { id: dto.companyId, tenantId },
      });
      if (!company) {
        throw new NotFoundException('Unternehmen nicht gefunden');
      }
    }

    const deal = await this.prisma.deal.create({
      data: {
        tenantId,
        pipelineId: dto.pipelineId,
        stageId: dto.stageId,
        contactId: dto.contactId,
        companyId: dto.companyId,
        title: dto.title,
        value: dto.value,
        currency: dto.currency ?? 'EUR',
        status: dto.status ?? 'OPEN',
        expectedCloseDate: dto.expectedCloseDate
          ? new Date(dto.expectedCloseDate)
          : undefined,
        notes: dto.notes,
        lostReason: dto.lostReason,
        lostReasonText: dto.lostReasonText,
        dealTypeId: dto.dealTypeId,
        createdBy: userId,
        owners: {
          create: { tenantId, userId, role: 'OWNER' },
        },
        ...(dto.projectRequest
          ? {
              projectRequest: {
                create: {
                  notes: dto.projectRequest.notes,
                  workload: dto.projectRequest.workload,
                  startDate: dto.projectRequest.startDate
                    ? new Date(dto.projectRequest.startDate)
                    : undefined,
                  duration: dto.projectRequest.duration,
                  onsitePercent: dto.projectRequest.onsitePercent,
                  rateRemote: dto.projectRequest.rateRemote,
                  rateOnsite: dto.projectRequest.rateOnsite,
                },
              },
            }
          : {}),
      },
      include: {
        pipeline: { select: { id: true, name: true } },
        stage: { select: { id: true, name: true, color: true } },
        contact: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            companyName: true,
          },
        },
        company: {
          select: {
            id: true,
            name: true,
          },
        },
        owners: true,
        projectRequest: true,
      },
    });

    // Event publizieren (async, nicht blockierend)
    this.eventPublisher.dealCreated(tenantId, deal.id, userId).catch(() => {});

    return deal;
  }

  async findAll(tenantId: string, query: QueryDealsDto) {
    const page = query.page ?? 1;
    const pageSize = query.pageSize ?? 25;

    const where: Prisma.DealWhereInput = { tenantId };

    if (query.pipelineId) {
      where.pipelineId = query.pipelineId;
    }
    if (query.stageId) {
      where.stageId = query.stageId;
    }
    if (query.contactId) {
      where.contactId = query.contactId;
    }
    if (query.companyId) {
      where.companyId = query.companyId;
    }
    if (query.status) {
      where.status = query.status;
    }
    if (query.search) {
      where.title = { contains: query.search, mode: 'insensitive' };
    }

    const allowedSortFields = [
      'createdAt',
      'updatedAt',
      'title',
      'value',
      'expectedCloseDate',
    ];
    const sortField = allowedSortFields.includes(query.sort ?? '')
      ? (query.sort as string)
      : 'createdAt';

    const [data, total] = await Promise.all([
      this.prisma.deal.findMany({
        where,
        skip: (page - 1) * pageSize,
        take: pageSize,
        orderBy: { [sortField]: query.order ?? 'desc' },
        include: {
          pipeline: { select: { id: true, name: true } },
          stage: { select: { id: true, name: true, color: true } },
          contact: {
            select: {
              id: true,
              firstName: true,
              lastName: true,
              companyName: true,
            },
          },
          company: {
            select: {
              id: true,
              name: true,
            },
          },
          owners: true,
        },
      }),
      this.prisma.deal.count({ where }),
    ]);

    return { data, total, page, pageSize };
  }

  async findOne(tenantId: string, id: string) {
    const deal = await this.prisma.deal.findFirst({
      where: { id, tenantId },
      include: {
        pipeline: { include: { stages: { orderBy: { sortOrder: 'asc' } } } },
        stage: true,
        contact: true,
        company: true,
        owners: true,
        projectRequest: true,
        dealVouchers: {
          include: {
            voucher: {
              select: {
                id: true,
                voucherType: true,
                voucherNumber: true,
                voucherDate: true,
                voucherStatus: true,
                totalGrossAmount: true,
                currency: true,
                title: true,
                lexwareDeepLink: true,
              },
            },
          },
          orderBy: { linkedAt: 'desc' },
        },
      },
    });

    if (!deal) {
      throw new NotFoundException('Vorgang nicht gefunden');
    }

    // Custom Fields anhaengen
    const customFields = await this.customFieldsService.getCustomFieldsForEntity(
      tenantId,
      CustomFieldEntityType.DEAL,
      id,
    );

    return { ...deal, customFields };
  }

  async update(
    tenantId: string,
    id: string,
    userId: string,
    dto: UpdateDealDto,
  ) {
    const existing = await this.findOne(tenantId, id);

    // Stage validieren wenn geaendert
    if (dto.stageId) {
      const pipelineId = dto.pipelineId ?? existing.pipelineId;
      const stage = await this.prisma.pipelineStage.findFirst({
        where: { id: dto.stageId, pipelineId },
      });
      if (!stage) {
        throw new NotFoundException('Pipeline-Stufe nicht gefunden');
      }
    }

    // Lost-Reason Validation
    if (dto.status === 'LOST') {
      // Wenn LOST gesetzt wird, muss lostReason vorhanden sein (im DTO oder bereits gesetzt)
      if (!dto.lostReason && !existing.lostReason) {
        throw new BadRequestException(
          'Verlustgrund (lostReason) ist Pflicht wenn Deal auf LOST gesetzt wird',
        );
      }
    }

    const { lostReason, lostReasonText, projectRequest, ...restDto } = dto;

    const updateData: Prisma.DealUpdateInput = {
      ...restDto,
      expectedCloseDate: dto.expectedCloseDate
        ? new Date(dto.expectedCloseDate)
        : undefined,
      updatedBy: userId,
    };

    // Lost-Reason Felder setzen
    if (lostReason !== undefined) {
      updateData.lostReason = lostReason;
    }
    if (lostReasonText !== undefined) {
      updateData.lostReasonText = lostReasonText;
    }

    // Wenn Deal gewonnen → lostReason und lostReasonText loeschen
    if (dto.status === 'WON') {
      updateData.lostReason = null;
      updateData.lostReasonText = null;
    }

    // Wenn Deal gewonnen/verloren, closedAt setzen
    if (dto.status === 'WON' || dto.status === 'LOST') {
      updateData.closedAt = new Date();
    }

    // ProjectRequest upsert (vor Deal-Update damit Include aktuelle Daten liefert)
    if (projectRequest !== undefined && projectRequest !== null) {
      await this.prisma.projectRequestDetails.upsert({
        where: { dealId: id },
        create: {
          dealId: id,
          notes: projectRequest.notes,
          workload: projectRequest.workload,
          startDate: projectRequest.startDate
            ? new Date(projectRequest.startDate)
            : undefined,
          duration: projectRequest.duration,
          onsitePercent: projectRequest.onsitePercent,
          rateRemote: projectRequest.rateRemote,
          rateOnsite: projectRequest.rateOnsite,
        },
        update: {
          notes: projectRequest.notes,
          workload: projectRequest.workload,
          startDate: projectRequest.startDate
            ? new Date(projectRequest.startDate)
            : undefined,
          duration: projectRequest.duration,
          onsitePercent: projectRequest.onsitePercent,
          rateRemote: projectRequest.rateRemote,
          rateOnsite: projectRequest.rateOnsite,
        },
      });
    }

    const updated = await this.prisma.deal.update({
      where: { id },
      data: updateData,
      include: {
        pipeline: { select: { id: true, name: true } },
        stage: { select: { id: true, name: true, color: true } },
        contact: {
          select: {
            id: true,
            firstName: true,
            lastName: true,
            companyName: true,
          },
        },
        company: {
          select: {
            id: true,
            name: true,
          },
        },
        owners: true,
        projectRequest: true,
      },
    });

    // Events publizieren (async, nicht blockierend)
    if (dto.stageId && dto.stageId !== existing.stageId) {
      this.eventPublisher.dealStageChanged(tenantId, id, userId, {
        previousStageId: existing.stageId,
        newStageId: dto.stageId,
      }).catch(() => {});
    }

    if (dto.status === 'WON') {
      this.eventPublisher.dealWon(tenantId, id, userId, {
        value: updated.value?.toNumber() ?? null,
        currency: updated.currency ?? 'EUR',
      }).catch(() => {});
    }

    if (dto.status === 'LOST') {
      this.eventPublisher.dealLost(tenantId, id, userId, {
        lostReason: updated.lostReason ?? null,
      }).catch(() => {});
    }

    return updated;
  }

  async remove(tenantId: string, id: string) {
    await this.findOne(tenantId, id);

    // Custom Field Values entfernen (entityId hat keinen FK, daher manuell)
    await this.prisma.customFieldValue.deleteMany({
      where: { tenantId, entityId: id },
    });

    return this.prisma.deal.delete({ where: { id } });
  }

  // --------------------------------------------------------
  // Forecast (gewichtete Pipeline-Aggregation)
  // --------------------------------------------------------

  async forecast(tenantId: string, query: ForecastQueryDto) {
    const period = query.period ?? ForecastPeriod.QUARTER;
    const { start, end } = this.getPeriodBounds(period);

    const where: Prisma.DealWhereInput = {
      tenantId,
      status: 'OPEN',
      expectedCloseDate: { gte: start, lte: end },
    };

    if (query.pipelineId) {
      where.pipelineId = query.pipelineId;
    }

    const deals = await this.prisma.deal.findMany({
      where,
      include: {
        stage: {
          select: {
            id: true,
            name: true,
            color: true,
            probability: true,
            sortOrder: true,
          },
        },
        pipeline: { select: { id: true, name: true } },
      },
    });

    // Nach Stage gruppieren
    const stageMap = new Map<string, {
      stageId: string;
      stageName: string;
      stageColor: string;
      probability: number;
      sortOrder: number;
      pipelineId: string;
      pipelineName: string;
      dealCount: number;
      totalValue: number;
      weightedValue: number;
    }>();

    for (const deal of deals) {
      const key = deal.stageId;
      const value = deal.value?.toNumber() ?? 0;
      const prob = deal.stage.probability?.toNumber() ?? 0;

      if (!stageMap.has(key)) {
        stageMap.set(key, {
          stageId: deal.stage.id,
          stageName: deal.stage.name,
          stageColor: deal.stage.color,
          probability: prob,
          sortOrder: deal.stage.sortOrder,
          pipelineId: deal.pipeline.id,
          pipelineName: deal.pipeline.name,
          dealCount: 0,
          totalValue: 0,
          weightedValue: 0,
        });
      }

      const entry = stageMap.get(key)!;
      entry.dealCount += 1;
      entry.totalValue += value;
      entry.weightedValue += value * prob;
    }

    const stages = Array.from(stageMap.values()).sort(
      (a, b) => a.sortOrder - b.sortOrder,
    );

    const totalDeals = stages.reduce((sum, s) => sum + s.dealCount, 0);
    const totalValue = stages.reduce((sum, s) => sum + s.totalValue, 0);
    const totalWeightedValue = stages.reduce((sum, s) => sum + s.weightedValue, 0);

    return {
      period,
      periodStart: start.toISOString(),
      periodEnd: end.toISOString(),
      currency: 'EUR',
      stages,
      totals: {
        dealCount: totalDeals,
        totalValue,
        weightedValue: totalWeightedValue,
      },
    };
  }

  private getPeriodBounds(period: ForecastPeriod): { start: Date; end: Date } {
    const now = new Date();

    switch (period) {
      case ForecastPeriod.MONTH: {
        const start = new Date(now.getFullYear(), now.getMonth(), 1);
        const end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
        return { start, end };
      }
      case ForecastPeriod.QUARTER: {
        const quarterStart = Math.floor(now.getMonth() / 3) * 3;
        const start = new Date(now.getFullYear(), quarterStart, 1);
        const end = new Date(now.getFullYear(), quarterStart + 3, 0, 23, 59, 59, 999);
        return { start, end };
      }
      case ForecastPeriod.YEAR: {
        const start = new Date(now.getFullYear(), 0, 1);
        const end = new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999);
        return { start, end };
      }
    }
  }
}
