import { BadRequestException, ConflictException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { AuditService } from '../audit/audit.service';
import { resolveEstateScope } from '../common/estate-scope';
import { getNumericPlanLimit } from '../common/plan-limits';
import { RequestUser } from '../common/request-user';
import { PrismaService } from '../database/prisma.service';
import { CreateGateDto, UpdateGateDto } from './dto/gate.dto';

@Injectable()
export class GatesService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly audit: AuditService
  ) {}

  findAll(user: RequestUser, estateId?: string) {
    const scopedEstateId = resolveEstateScope(user, estateId);
    return this.prisma.estateGate.findMany({
      where: { estateId: scopedEstateId },
      include: {
        _count: {
          select: { guards: true, sessions: true }
        }
      },
      orderBy: [{ isActive: 'desc' }, { name: 'asc' }]
    });
  }

  async create(dto: CreateGateDto, user: RequestUser) {
    const estateId = resolveEstateScope(user, dto.estateId);
    await this.assertGateLimit(estateId);
    try {
      const gate = await this.prisma.estateGate.create({
        data: {
          estateId,
          name: dto.name.trim(),
          type: dto.type,
          description: dto.description,
          metadata: this.toJson(dto.metadata)
        }
      });

      await this.audit.record({
        estateId,
        actorType: 'USER',
        actorId: user.id,
        action: 'ESTATE_GATE_CREATED',
        metadata: { gateId: gate.id, name: gate.name, type: gate.type }
      });

      return gate;
    } catch (error) {
      if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
        throw new ConflictException('A gate with this name already exists in the estate');
      }
      throw error;
    }
  }

  async update(id: string, dto: UpdateGateDto, user: RequestUser) {
    const existing = await this.prisma.estateGate.findUnique({ where: { id } });
    if (!existing) {
      throw new NotFoundException('Gate not found');
    }
    resolveEstateScope(user, existing.estateId);

    const gate = await this.prisma.estateGate.update({
      where: { id },
      data: {
        name: dto.name?.trim(),
        type: dto.type,
        description: dto.description,
        isActive: dto.isActive,
        metadata: this.toJson(dto.metadata)
      }
    });

    await this.audit.record({
      estateId: gate.estateId,
      actorType: 'USER',
      actorId: user.id,
      action: 'ESTATE_GATE_UPDATED',
      metadata: { gateId: gate.id, isActive: gate.isActive }
    });

    return gate;
  }

  private async assertGateLimit(estateId: string) {
    const estate = await this.prisma.estate.findUnique({
      where: { id: estateId },
      select: {
        subscriptionPlan: { select: { limits: true, name: true } },
        _count: { select: { gates: true } }
      }
    });
    if (!estate) {
      throw new NotFoundException('Estate not found');
    }
    const limit = getNumericPlanLimit(estate.subscriptionPlan?.limits, 'gates');
    if (limit !== null && estate._count.gates >= limit) {
      throw new BadRequestException(`Gate limit reached for ${estate.subscriptionPlan?.name ?? 'this plan'}`);
    }
  }

  private toJson(value: unknown) {
    if (value === undefined) {
      return undefined;
    }
    return JSON.parse(JSON.stringify(value)) as Prisma.InputJsonValue;
  }
}
