import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  Logger,
  NotFoundException,
} from '@nestjs/common';
import { Prisma, ViharStatus } from '@prisma/client';
import { PrismaService } from '../../common/prisma/prisma.service';
import { RoutesService } from '../routes/routes.service';
import type { CreateViharInput, ListViharsQuery } from '@vihar/shared';

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

  constructor(
    private readonly prisma: PrismaService,
    private readonly routes: RoutesService,
  ) {}

  async create(cityId: number, createdBy: number, input: CreateViharInput) {
    // Resolve departure location -> denorm locality
    const fromLocation = await this.prisma.location.findUnique({
      where: { locationId: input.departureLocationId },
    });
    if (!fromLocation || fromLocation.cityId !== cityId) {
      throw new NotFoundException('Departure location not found');
    }
    const toLocation = await this.prisma.location.findUnique({
      where: { locationId: input.arrivalLocationId },
    });
    if (!toLocation || toLocation.cityId !== cityId) {
      throw new NotFoundException('Arrival location not found');
    }

    // Best-effort planned distance/duration from cache (or skip if Google not configured yet)
    let plannedDistanceMeters: number | null = null;
    let plannedDurationSec: number | null = null;
    try {
      const route = await this.routes.getWalkingByLocationIds(
        cityId,
        input.departureLocationId,
        input.arrivalLocationId,
      );
      plannedDistanceMeters = route.distanceMeters;
      plannedDurationSec = route.durationSeconds;
    } catch (e) {
      this.logger.warn(`Could not pre-compute route distance: ${(e as Error).message}`);
    }

    return this.prisma.vihar.create({
      data: {
        cityId,
        viharDate: new Date(input.viharDate + 'T00:00:00Z'),
        plannedStartTime: input.plannedStartTime + ':00', // HH:MM:SS
        sadhujiCount: input.sadhujiCount,
        sadhvijiCount: input.sadhvijiCount,
        otherCount: input.otherCount,
        headSaintHonorific: input.headSaintHonorific ?? null,
        headSaintName: input.headSaintName,
        samudayId: input.samudayId ?? null,
        departureLocationId: input.departureLocationId,
        arrivalLocationId: input.arrivalLocationId,
        departureLocalityId: fromLocation.localityId,
        templeDarshan: input.templeDarshan,
        updhi: input.updhi,
        remarks: input.remarks ?? null,
        plannedDistanceMeters,
        plannedDurationSec,
        status: ViharStatus.planned,
        createdBy,
      },
      include: this.detailInclude(),
    });
  }

  async list(cityId: number, q: ListViharsQuery, userId?: number) {
    const where: Prisma.ViharWhereInput = { cityId };
    if (q.date) where.viharDate = new Date(q.date + 'T00:00:00Z');
    else if (q.dateFrom || q.dateTo) {
      where.viharDate = {};
      if (q.dateFrom) (where.viharDate as Prisma.DateTimeFilter).gte = new Date(q.dateFrom + 'T00:00:00Z');
      if (q.dateTo) (where.viharDate as Prisma.DateTimeFilter).lte = new Date(q.dateTo + 'T00:00:00Z');
    }
    if (q.status) where.status = q.status;
    if (q.localityId) where.departureLocalityId = q.localityId;
    if (q.samudayId) where.samudayId = q.samudayId;
    if (userId) where.allocations = { some: { userId, isActive: true } };
    if (q.allocated === false) where.allocations = { none: { isActive: true } };
    else if (q.allocated === true && !userId) where.allocations = { some: { isActive: true } };

    const [items, total] = await this.prisma.$transaction([
      this.prisma.vihar.findMany({
        where,
        skip: (q.page - 1) * q.pageSize,
        take: q.pageSize,
        orderBy: [{ viharDate: 'desc' }, { plannedStartTime: 'asc' }],
        include: this.listInclude(),
      }),
      this.prisma.vihar.count({ where }),
    ]);
    return { items, total, page: q.page, pageSize: q.pageSize };
  }

  async getById(cityId: number, viharId: number) {
    const v = await this.prisma.vihar.findUnique({
      where: { viharId },
      include: this.detailInclude(),
    });
    if (!v || v.cityId !== cityId) throw new NotFoundException('Vihar not found');
    return v;
  }

  async cancel(cityId: number, viharId: number, actorId: number, reason: string) {
    const v = await this.getById(cityId, viharId);
    if (v.status === ViharStatus.cancelled) throw new BadRequestException('Already cancelled');
    if (v.status === ViharStatus.completed) {
      throw new BadRequestException('Cannot cancel a completed vihar');
    }
    return this.prisma.vihar.update({
      where: { viharId },
      data: {
        status: ViharStatus.cancelled,
        cancelReason: reason,
        closedByUserId: actorId,
      },
      include: this.detailInclude(),
    });
  }

  async closeManual(
    cityId: number,
    viharId: number,
    actorId: number,
    input: { startTime: string; endTime: string; distanceKm: number; notes?: string },
  ) {
    const v = await this.getById(cityId, viharId);
    if (v.status === ViharStatus.completed || v.status === ViharStatus.cancelled) {
      throw new BadRequestException(`Cannot close - vihar is ${v.status}`);
    }
    const startAt = this.combineDateTime(v.viharDate, input.startTime);
    const endAt = this.combineDateTime(v.viharDate, input.endTime);
    if (endAt <= startAt) {
      throw new BadRequestException('End time must be after start time');
    }
    return this.prisma.vihar.update({
      where: { viharId },
      data: {
        status: ViharStatus.completed,
        actualStartAt: startAt,
        actualEndAt: endAt,
        actualDistanceKm: input.distanceKm,
        closedByUserId: actorId,
        closedByCaptain: true,
      },
      include: this.detailInclude(),
    });
  }

  async reopen(cityId: number, viharId: number, actorId: number) {
    const v = await this.getById(cityId, viharId);
    if (v.status !== ViharStatus.completed && v.status !== ViharStatus.auto_closed) {
      throw new BadRequestException('Only completed or auto-closed vihars can be reopened');
    }
    // 24-hour window
    const closedAt = v.updatedAt;
    if (Date.now() - closedAt.getTime() > 24 * 60 * 60 * 1000) {
      throw new ForbiddenException('Reopen window expired (24 hours after close)');
    }
    this.logger.log(`Vihar ${viharId} reopened by user ${actorId}`);
    return this.prisma.vihar.update({
      where: { viharId },
      data: { status: ViharStatus.in_progress },
      include: this.detailInclude(),
    });
  }

  private combineDateTime(date: Date, hhmm: string): Date {
    const [h, m] = hhmm.split(':').map((x) => parseInt(x!, 10));
    const d = new Date(date);
    d.setUTCHours(h ?? 0, m ?? 0, 0, 0);
    return d;
  }

  private listInclude() {
    return {
      departureLocation: { select: { locationId: true, name: true } },
      arrivalLocation: { select: { locationId: true, name: true } },
      samuday: { select: { samudayId: true, name: true } },
      departureLocality: { select: { localityId: true, name: true } },
      _count: { select: { allocations: { where: { isActive: true } } } },
    } satisfies Prisma.ViharInclude;
  }

  private detailInclude() {
    return {
      departureLocation: true,
      arrivalLocation: true,
      samuday: true,
      departureLocality: true,
      creator: { select: { userId: true, fullName: true } },
      closedBy: { select: { userId: true, fullName: true } },
      allocations: {
        where: { isActive: true },
        include: {
          user: {
            select: {
              userId: true,
              fullName: true,
              phone: true,
              homeLocality: { select: { localityId: true, name: true } },
            },
          },
        },
      },
    } satisfies Prisma.ViharInclude;
  }
}
