import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { VisitorPassStatus, VisitorPassType, VisitorRiskLevel } from '@prisma/client';
import { customAlphabet } from 'nanoid';
import { AuditService } from '../audit/audit.service';
import { resolveEstateScope } from '../common/estate-scope';
import { getNumericPlanLimit, startOfCurrentMonth } from '../common/plan-limits';
import { RequestUser } from '../common/request-user';
import { PrismaService } from '../database/prisma.service';
import { CreateVisitorDto } from './dto/create-visitor.dto';
import { CreateVisitorPassDto } from './dto/create-visitor-pass.dto';
import { UpdateVisitorRiskDto } from './dto/update-visitor-risk.dto';

const visitorCode = customAlphabet('ABCDEFGHJKLMNPQRSTUVWXYZ23456789', 8);
const qrToken = customAlphabet('ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789', 32);

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

  findVisitors(user: RequestUser, estateId?: string, q?: string) {
    const scopedEstateId = resolveEstateScope(user, estateId);
    return this.prisma.visitor.findMany({
      where: {
        estateId: scopedEstateId,
        ...(q
          ? {
            OR: [
              { name: { contains: q, mode: 'insensitive' } },
              { phone: { contains: q, mode: 'insensitive' } },
              { vehiclePlate: { contains: q, mode: 'insensitive' } }
            ]
          }
          : {})
      },
      orderBy: { createdAt: 'desc' },
      take: 50
    });
  }

  async createVisitor(dto: CreateVisitorDto, user: RequestUser) {
    const estateId = resolveEstateScope(user, dto.estateId);
    const visitor = await this.prisma.visitor.create({
      data: {
        estateId,
        name: dto.name,
        phone: dto.phone,
        vehiclePlate: dto.vehiclePlate,
        photoUrl: dto.photoUrl
      }
    });
    await this.audit.record({
      estateId,
      actorType: 'USER',
      actorId: user.id,
      action: 'VISITOR_CREATED',
      metadata: { visitorId: visitor.id }
    });
    return visitor;
  }

  findPasses(user: RequestUser, estateId?: string) {
    const scopedEstateId = resolveEstateScope(user, estateId);
    return this.prisma.visitorPass.findMany({
      where: { estateId: scopedEstateId },
      include: { visitor: true, hostResident: true },
      orderBy: { createdAt: 'desc' },
      take: 100
    });
  }

  async createPass(dto: CreateVisitorPassDto, user: RequestUser) {
    const estateId = resolveEstateScope(user, dto.estateId);
    await this.assertMonthlyPassLimit(estateId);
    if (dto.validUntil <= dto.validFrom) {
      throw new BadRequestException('validUntil must be after validFrom');
    }

    const [host, visitor] = await Promise.all([
      this.prisma.resident.findFirst({
        where: { id: dto.hostResidentId, estateId }
      }),
      this.prisma.visitor.findFirst({
        where: { id: dto.visitorId, estateId }
      })
    ]);
    if (!host) {
      throw new NotFoundException('Host resident was not found in this estate');
    }
    if (!visitor) {
      throw new NotFoundException('Visitor was not found in this estate');
    }

    const maxEntries = dto.maxEntries ?? this.defaultMaxEntries(dto.passType);
    const pass = await this.prisma.visitorPass.create({
      data: {
        estateId,
        visitorId: dto.visitorId,
        hostResidentId: dto.hostResidentId,
        visitorCode: visitorCode(),
        qrToken: qrToken(),
        validFrom: dto.validFrom,
        validUntil: dto.validUntil,
        passType: dto.passType,
        maxEntries
      },
      include: { visitor: true, hostResident: true }
    });

    await this.audit.record({
      estateId,
      actorType: 'USER',
      actorId: user.id,
      action: 'VISITOR_PASS_CREATED',
      metadata: { visitorPassId: pass.id, visitorCode: pass.visitorCode }
    });

    return {
      ...pass,
      qrPayload: { token: pass.qrToken }
    };
  }

  async revokePass(id: string, user: RequestUser) {
    const existing = await this.prisma.visitorPass.findUnique({ where: { id } });
    if (!existing) {
      throw new NotFoundException('Visitor pass not found');
    }
    resolveEstateScope(user, existing.estateId);

    const pass = await this.prisma.visitorPass.update({
      where: { id },
      data: { status: VisitorPassStatus.REVOKED, revokedAt: new Date() }
    });
    await this.audit.record({
      estateId: pass.estateId,
      actorType: 'USER',
      actorId: user.id,
      action: 'VISITOR_PASS_REVOKED',
      metadata: { visitorPassId: pass.id }
    });
    return pass;
  }

  async getVisitorHistory(id: string, user: RequestUser) {
    const visitor = await this.prisma.visitor.findUnique({
      where: { id },
      include: {
        passes: {
          include: {
            hostResident: true,
            sessions: { include: { guard: true, gate: true }, orderBy: { checkinTime: 'desc' } }
          },
          orderBy: { createdAt: 'desc' }
        },
        riskEvents: { orderBy: { createdAt: 'desc' }, take: 25 }
      }
    });
    if (!visitor) {
      throw new NotFoundException('Visitor not found');
    }
    if (visitor.estateId) {
      resolveEstateScope(user, visitor.estateId);
    }

    const sessions = visitor.passes.flatMap((pass) => pass.sessions.map((session) => ({ ...session, hostResident: pass.hostResident })));
    const checkouts = sessions.filter((session) => session.checkoutTime).length;
    const open = sessions.filter((session) => !session.checkoutTime).length;
    const residentsVisited = Array.from(new Set(visitor.passes.map((pass) => pass.hostResidentId)));
    const gatesUsed = Array.from(new Set(sessions.map((session) => session.gate?.name ?? session.entryGate).filter(Boolean)));

    return {
      visitor,
      intelligence: {
        totalVisits: sessions.length,
        checkouts,
        openVisits: open,
        exitReliabilityPercent: sessions.length ? Math.round((checkouts / sessions.length) * 100) : null,
        residentsVisited: residentsVisited.length,
        gatesUsed,
        repeatVisitor: sessions.length > 1,
        riskLevel: visitor.riskLevel,
        riskScore: visitor.riskScore,
        flagged: visitor.flagged,
        blacklisted: visitor.blacklisted
      }
    };
  }

  async updateRisk(id: string, dto: UpdateVisitorRiskDto, user: RequestUser) {
    const existing = await this.prisma.visitor.findUnique({ where: { id } });
    if (!existing) {
      throw new NotFoundException('Visitor not found');
    }
    if (existing.estateId) {
      resolveEstateScope(user, existing.estateId);
    }
    const nextScore = dto.riskScore ?? existing.riskScore;
    const nextLevel = dto.riskLevel ?? this.riskLevelFromScore(nextScore);
    const visitor = await this.prisma.$transaction(async (tx) => {
      const updated = await tx.visitor.update({
        where: { id },
        data: {
          riskScore: nextScore,
          riskLevel: nextLevel,
          flagged: dto.flagged,
          blacklisted: dto.blacklisted,
          riskNotes: dto.riskNotes
        }
      });
      await tx.visitorRiskEvent.create({
        data: {
          visitorId: id,
          estateId: existing.estateId,
          actorId: user.id,
          score: nextScore,
          level: nextLevel,
          reason: dto.riskNotes ?? 'Risk profile updated',
          metadata: {
            flagged: dto.flagged,
            blacklisted: dto.blacklisted
          }
        }
      });
      return updated;
    });

    await this.audit.record({
      estateId: existing.estateId,
      actorType: 'USER',
      actorId: user.id,
      action: 'VISITOR_RISK_UPDATED',
      metadata: { visitorId: id, riskScore: visitor.riskScore, riskLevel: visitor.riskLevel }
    });

    return visitor;
  }

  private async assertMonthlyPassLimit(estateId: string) {
    const estate = await this.prisma.estate.findUnique({
      where: { id: estateId },
      select: { subscriptionPlan: { select: { limits: true } } }
    });
    const limit = getNumericPlanLimit(estate?.subscriptionPlan?.limits, 'visitorPassesPerMonth');
    if (limit === null) {
      return;
    }
    const used = await this.prisma.visitorPass.count({
      where: { estateId, createdAt: { gte: startOfCurrentMonth() } }
    });
    if (used >= limit) {
      throw new BadRequestException('Monthly visitor pass limit for this estate plan has been reached');
    }
  }

  private riskLevelFromScore(score: number) {
    if (score >= 70) {
      return VisitorRiskLevel.HIGH;
    }
    if (score >= 35) {
      return VisitorRiskLevel.MEDIUM;
    }
    return VisitorRiskLevel.LOW;
  }

  private defaultMaxEntries(passType: VisitorPassType) {
    if (passType === VisitorPassType.SINGLE_USE) {
      return 1;
    }
    return 999;
  }

}
