import { Injectable, BadRequestException } from '@nestjs/common';
import { CrmDeal } from '@prisma/client';
import { PrismaService } from '../../../../prisma/prisma.service';
import { EntityManager } from '../../core/entity-manager.base';
import { EntityEventsService } from '../../core/entity-events.service';
import { EntityContext } from '../../core/entity-context.interface';
import { DealPermissions } from './deal.permissions';
import { PipelinesService } from '../../services/pipelines.service';
import { UserFieldsService } from '../../services/user-fields.service';
import { MoveDealDto } from '../../dto/move-deal.dto';
import { LoseDealDto } from '../../dto/lose-deal.dto';

const DEAL_INCLUDE = {
  pipeline: true,
  stage: true,
  assignee: { select: { id: true, firstName: true, lastName: true } },
  createdBy: { select: { id: true, firstName: true, lastName: true } },
  lostReason: true,
  _count: {
    select: { activities: true, trainings: true, timeline: true },
  },
};

@Injectable()
export class DealManager extends EntityManager<CrmDeal & { id: string }> {
  readonly entityType = 'deal';
  readonly modelName = 'crmDeal';
  readonly include = DEAL_INCLUDE;

  constructor(
    prisma: PrismaService,
    events: EntityEventsService,
    permissions: DealPermissions,
    private readonly pipelinesService: PipelinesService,
    private readonly userFieldsService: UserFieldsService,
  ) {
    super(prisma, events, permissions);
  }

  /** Override getById to include custom field values */
  async getById(id: string): Promise<any> {
    const entity = await super.getById(id);
    if (!entity) return null;
    const customFieldValues = await this.userFieldsService.getValuesForEntity(id, 'deal');
    return { ...entity, customFieldValues };
  }

  /** Get deal by sequential number (per club) */
  async getByNumber(number: number, clubId?: string | null): Promise<any> {
    const entity = await this.prisma.crmDeal.findFirst({
      where: { number, ...(clubId ? { clubId } : {}), deletedAt: null },
      include: DEAL_INCLUDE,
    });
    if (!entity) return null;
    const customFieldValues = await this.userFieldsService.getValuesForEntity(entity.id, 'deal');
    return { ...entity, customFieldValues };
  }

  // -----------------------------------------------------------------------
  // Hooks
  // -----------------------------------------------------------------------

  protected async onBeforeAdd(
    fields: Record<string, any>,
    _context?: EntityContext,
  ): Promise<Record<string, any>> {
    // Set default pipeline/stage if not provided
    if (!fields.pipelineId) {
      const clubId = fields.clubId || null;
      const pipeline = await this.pipelinesService.getDefaultPipeline(clubId);
      if (!pipeline) {
        throw new BadRequestException('No default pipeline configured. Create a pipeline first.');
      }
      fields.pipelineId = pipeline.id;
    }

    if (!fields.stageId) {
      const defaultStage = await this.pipelinesService.getDefaultStage(fields.pipelineId);
      if (!defaultStage) {
        throw new BadRequestException('No default stage in pipeline. Configure stages first.');
      }
      fields.stageId = defaultStage.id;
    }

    // Auto-generate sequential deal number per club
    const maxResult = await this.prisma.crmDeal.aggregate({
      where: { clubId: fields.clubId || null },
      _max: { number: true },
    });
    fields.number = (maxResult._max.number || 0) + 1;

    // Extract customFieldValues (will be saved in onAfterAdd)
    // Don't pass to Prisma create
    if (fields.customFieldValues) {
      this._pendingCustomFields = fields.customFieldValues;
      delete fields.customFieldValues;
    }

    return fields;
  }

  private _pendingCustomFields: Record<string, any> | null = null;

  protected async onAfterAdd(entity: any, context?: EntityContext): Promise<void> {
    // Save custom field values if provided
    if (this._pendingCustomFields) {
      await this.userFieldsService.setValuesForEntity(
        entity.id,
        'deal',
        entity.clubId,
        this._pendingCustomFields,
      );
      this._pendingCustomFields = null;
    }

    // Create initial stage history entry
    await this.prisma.crmStageHistory.create({
      data: {
        dealId: entity.id,
        toStageId: entity.stageId,
        movedById: context?.userId || entity.createdById,
      },
    });

    // Create system timeline entry
    await this.prisma.crmTimeline.create({
      data: {
        dealId: entity.id,
        userId: context?.userId || entity.createdById,
        type: 'SYSTEM',
        subject: 'Сделка создана',
        content: `Сделка "${entity.title}" создана`,
        metadata: { source: entity.source },
      },
    });

    // Auto-create activity if stage has autoActivityType
    if (entity.stage?.autoActivityType) {
      await this.createAutoActivity(entity, entity.stage);
    }

    // Emit domain event
    await this.events.emit('crm.deal.created', {
      clubId: entity.clubId,
      deal: entity,
    });
  }

  protected async onBeforeUpdate(
    _id: string,
    fields: Record<string, any>,
    _oldEntity: any,
    _context?: EntityContext,
  ): Promise<Record<string, any>> {
    // Extract customFieldValues — save after update
    if (fields.customFieldValues) {
      this._pendingCustomFields = fields.customFieldValues;
      delete fields.customFieldValues;
    }
    return fields;
  }

  protected async onAfterUpdate(
    entity: any,
    oldEntity: any,
    context?: EntityContext,
  ): Promise<void> {
    // Save custom field values if provided
    if (this._pendingCustomFields) {
      await this.userFieldsService.setValuesForEntity(
        entity.id,
        'deal',
        entity.clubId,
        this._pendingCustomFields,
      );
      this._pendingCustomFields = null;
    }

    // Stage changed
    if (entity.stageId !== oldEntity.stageId) {
      await this.handleStageChange(entity, oldEntity, context);
    }
  }

  // -----------------------------------------------------------------------
  // Special operations
  // -----------------------------------------------------------------------

  async move(id: string, dto: MoveDealDto, userId: string): Promise<CrmDeal> {
    const deal = await this.getById(id);
    if (!deal) {
      throw new BadRequestException(`Deal ${id} not found`);
    }

    const targetStage = await this.prisma.crmStage.findUnique({
      where: { id: dto.stageId },
    });
    if (!targetStage) {
      throw new BadRequestException(`Stage ${dto.stageId} not found`);
    }

    const data: Record<string, any> = { stageId: dto.stageId };

    // If moving to WON/LOST, set closedAt
    if (targetStage.type === 'WON' || targetStage.type === 'LOST') {
      data.closedAt = new Date();
    } else if (deal.closedAt) {
      // Reopening deal
      data.closedAt = null;
    }

    return this.update(id, data, { userId, skipPermissions: true });
  }

  async win(id: string, userId: string): Promise<CrmDeal> {
    const deal = await this.getById(id);
    if (!deal) throw new BadRequestException(`Deal ${id} not found`);

    // Find WON stage in this pipeline
    const wonStage = await this.prisma.crmStage.findFirst({
      where: {
        pipelineId: deal.pipelineId,
        type: 'WON',
      },
    });
    if (!wonStage) {
      throw new BadRequestException('No WON stage configured in pipeline');
    }

    return this.update(
      id,
      { stageId: wonStage.id, closedAt: new Date() },
      { userId, skipPermissions: true },
    );
  }

  async lose(id: string, dto: LoseDealDto, userId: string): Promise<CrmDeal> {
    const deal = await this.getById(id);
    if (!deal) throw new BadRequestException(`Deal ${id} not found`);

    const lostStage = await this.prisma.crmStage.findFirst({
      where: {
        pipelineId: deal.pipelineId,
        type: 'LOST',
      },
    });
    if (!lostStage) {
      throw new BadRequestException('No LOST stage configured in pipeline');
    }

    return this.update(
      id,
      {
        stageId: lostStage.id,
        closedAt: new Date(),
        lostReasonId: dto.lostReasonId,
        lostComment: dto.lostComment,
      },
      { userId, skipPermissions: true },
    );
  }

  /** Get deals grouped by stage (for kanban view) */
  async getKanban(where: Record<string, any>, pipelineId: string): Promise<Record<string, any[]>> {
    const stages = await this.prisma.crmStage.findMany({
      where: { pipelineId },
      orderBy: { position: 'asc' },
    });

    const deals = await this.prisma.crmDeal.findMany({
      where: { ...where, pipelineId, deletedAt: null },
      include: DEAL_INCLUDE,
      orderBy: { updatedAt: 'desc' },
    });

    const kanban: Record<string, any> = {};
    for (const stage of stages) {
      kanban[stage.id] = {
        stage,
        deals: deals.filter((d) => d.stageId === stage.id),
      };
    }
    return kanban;
  }

  // -----------------------------------------------------------------------
  // Private
  // -----------------------------------------------------------------------

  private async handleStageChange(
    entity: any,
    oldEntity: any,
    context?: EntityContext,
  ): Promise<void> {
    // Calculate duration on previous stage
    const lastHistory = await this.prisma.crmStageHistory.findFirst({
      where: { dealId: entity.id },
      orderBy: { movedAt: 'desc' },
    });
    const duration = lastHistory
      ? Math.floor((Date.now() - lastHistory.movedAt.getTime()) / 1000)
      : null;

    // Create stage history entry
    await this.prisma.crmStageHistory.create({
      data: {
        dealId: entity.id,
        fromStageId: oldEntity.stageId,
        toStageId: entity.stageId,
        movedById: context?.userId,
        duration,
      },
    });

    // Timeline entry
    await this.prisma.crmTimeline.create({
      data: {
        dealId: entity.id,
        userId: context?.userId,
        type: 'STAGE_CHANGE',
        subject: 'Смена стадии',
        metadata: {
          fromStageId: oldEntity.stageId,
          fromStageName: oldEntity.stage?.name,
          toStageId: entity.stageId,
          toStageName: entity.stage?.name,
        },
      },
    });

    // Auto-create activity for new stage
    if (entity.stage?.autoActivityType) {
      await this.createAutoActivity(entity, entity.stage);
    }

    // Emit domain events for WON/LOST
    if (entity.stage?.type === 'WON') {
      await this.events.emit('crm.deal.won', {
        clubId: entity.clubId,
        deal: entity,
      });
    } else if (entity.stage?.type === 'LOST') {
      await this.events.emit('crm.deal.lost', {
        clubId: entity.clubId,
        deal: entity,
      });
    }
  }

  private async createAutoActivity(deal: any, stage: any): Promise<void> {
    if (!deal.assigneeId) return;

    const scheduledAt = new Date();
    if (stage.autoActivityDays) {
      scheduledAt.setDate(scheduledAt.getDate() + stage.autoActivityDays);
    }

    await this.prisma.crmActivity.create({
      data: {
        dealId: deal.id,
        assigneeId: deal.assigneeId,
        type: stage.autoActivityType,
        subject: `${stage.autoActivityType === 'CALL' ? 'Позвонить' : 'Встреча'}: ${deal.contactName}`,
        scheduledAt,
      },
    });

    // Timeline entry for auto-activity
    await this.prisma.crmTimeline.create({
      data: {
        dealId: deal.id,
        type: 'ACTIVITY_CREATED',
        subject: 'Дело создано автоматически',
        metadata: {
          activityType: stage.autoActivityType,
          scheduledAt: scheduledAt.toISOString(),
        },
      },
    });
  }
}
