import { Injectable, NotFoundException, BadRequestException, Logger } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../../prisma/prisma.service';
import { BotScenario } from '../interfaces/scenario.interface';

@Injectable()
export class ScenarioService {
  private readonly logger = new Logger(ScenarioService.name);

  constructor(private readonly prisma: PrismaService) {}

  async create(clubId: string, name: string, description?: string) {
    const emptyScenario: BotScenario = {
      id: '',
      version: 1,
      name,
      startNodeId: 'node_start',
      variables: {},
      nodes: {
        node_start: {
          id: 'node_start',
          type: 'message',
          data: {
            text: '👋 Привет! Это новый бот.',
            buttons: [],
          },
        },
      },
    };

    return this.prisma.tgBotScenario.create({
      data: {
        clubId,
        name,
        description,
        scenario: emptyScenario as unknown as Prisma.InputJsonValue,
      },
    });
  }

  async findAll(clubId: string) {
    return this.prisma.tgBotScenario.findMany({
      where: { clubId },
      orderBy: { updatedAt: 'desc' },
      select: {
        id: true,
        name: true,
        description: true,
        version: true,
        isPublished: true,
        isDraft: true,
        createdAt: true,
        updatedAt: true,
      },
    });
  }

  async findOne(clubId: string, id: string) {
    const scenario = await this.prisma.tgBotScenario.findFirst({
      where: { id, clubId },
    });
    if (!scenario) throw new NotFoundException('Scenario not found');
    return scenario;
  }

  async update(
    clubId: string,
    id: string,
    data: { name?: string; description?: string; scenario?: unknown; positions?: unknown },
  ) {
    const existing = await this.prisma.tgBotScenario.findFirst({ where: { id, clubId } });
    if (!existing) throw new NotFoundException('Scenario not found');

    const updateData: Prisma.TgBotScenarioUpdateInput = {};
    if (data.name) updateData.name = data.name;
    if (data.description !== undefined) updateData.description = data.description;
    if (data.scenario) {
      this.validateScenario(data.scenario as BotScenario);
      updateData.scenario = data.scenario as Prisma.InputJsonValue;
      updateData.version = { increment: 1 };
      updateData.isDraft = true;
      updateData.isPublished = false;
    }
    if (data.positions !== undefined) {
      updateData.positions = data.positions as Prisma.InputJsonValue;
    }

    return this.prisma.tgBotScenario.update({ where: { id }, data: updateData });
  }

  async publish(clubId: string, id: string) {
    const scenario = await this.prisma.tgBotScenario.findFirst({ where: { id, clubId } });
    if (!scenario) throw new NotFoundException('Scenario not found');

    this.validateScenario(scenario.scenario as unknown as BotScenario);

    return this.prisma.tgBotScenario.update({
      where: { id },
      data: { isPublished: true, isDraft: false },
    });
  }

  async duplicate(clubId: string, id: string) {
    const original = await this.prisma.tgBotScenario.findFirst({ where: { id, clubId } });
    if (!original) throw new NotFoundException('Scenario not found');

    return this.prisma.tgBotScenario.create({
      data: {
        clubId,
        name: `${original.name} (копия)`,
        description: original.description,
        scenario: original.scenario as Prisma.InputJsonValue,
      },
    });
  }

  async remove(clubId: string, id: string) {
    const scenario = await this.prisma.tgBotScenario.findFirst({ where: { id, clubId } });
    if (!scenario) throw new NotFoundException('Scenario not found');

    // Check if any bot uses this scenario
    const botCount = await this.prisma.tgBot.count({ where: { scenarioId: id } });
    if (botCount > 0) {
      throw new BadRequestException(`Cannot delete: ${botCount} bot(s) use this scenario`);
    }

    await this.prisma.tgBotScenario.delete({ where: { id } });
    return { deleted: true };
  }

  /** Validate JSON scenario structure */
  validateScenario(scenario: BotScenario): void {
    if (!scenario) throw new BadRequestException('Scenario is empty');
    if (!scenario.startNodeId) throw new BadRequestException('startNodeId is required');
    if (!scenario.nodes || typeof scenario.nodes !== 'object') {
      throw new BadRequestException('nodes must be an object');
    }

    if (!scenario.nodes[scenario.startNodeId]) {
      throw new BadRequestException(`Start node "${scenario.startNodeId}" not found in nodes`);
    }

    const validTypes = ['message', 'condition', 'action', 'input', 'delay', 'referral'];
    const nodeIds = new Set(Object.keys(scenario.nodes));

    for (const [id, node] of Object.entries(scenario.nodes)) {
      if (!node.type || !validTypes.includes(node.type)) {
        throw new BadRequestException(`Node "${id}" has invalid type: ${node.type}`);
      }

      // Check that targets reference existing nodes
      if (node.next && !nodeIds.has(node.next)) {
        throw new BadRequestException(`Node "${id}" references non-existent next: "${node.next}"`);
      }

      if (node.type === 'condition') {
        for (const rule of node.data.rules) {
          if (rule.target && !nodeIds.has(rule.target)) {
            throw new BadRequestException(
              `Node "${id}" condition references non-existent target: "${rule.target}"`,
            );
          }
        }
      }

      if (node.type === 'message' && node.data.buttons) {
        for (const btn of node.data.buttons) {
          if (btn.action === 'goto' && btn.target && !nodeIds.has(btn.target)) {
            throw new BadRequestException(
              `Node "${id}" button references non-existent target: "${btn.target}"`,
            );
          }
        }
      }
    }
  }
}
