import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateComplexDto } from './dto/create-complex.dto';
import { CreateSpatialUnitDto } from './dto/create-spatial-unit.dto';
import { CreateRepartitionKeyDto } from './dto/create-repartition-key.dto';
import { GenerateLayoutDto } from './dto/generate-layout.dto';
import { LotType, Prisma, SpatialUnitType } from '@prisma/client';

@Injectable()
export class ComplexesService {
  constructor(private readonly prisma: PrismaService) {}

  async create(tenantId: string, dto: CreateComplexDto) {
    const complex = await this.prisma.complex.create({
      data: {
        tenantId,
        name: dto.name,
        type: dto.type,
        address: dto.address,
        city: dto.city,
        country: dto.country ?? 'MA',
        gpsLat: dto.gpsLat,
        gpsLng: dto.gpsLng,
        repartitionKeys: {
          create: [
            { code: 'GEN', label: 'Charges générales', type: 'GENERALE' },
          ],
        },
      },
      include: { repartitionKeys: true },
    });
    return complex;
  }

  findAllForTenant(tenantId: string | null) {
    return this.prisma.complex.findMany({
      where: tenantId ? { tenantId } : undefined,
      include: {
        _count: { select: { spatialUnits: true } },
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  async findOne(id: string, tenantId: string | null, isSuperAdmin: boolean) {
    const complex = await this.prisma.complex.findUnique({
      where: { id },
      include: {
        spatialUnits: { include: { lots: true } },
        repartitionKeys: true,
      },
    });
    if (!complex) throw new NotFoundException();
    if (!isSuperAdmin && complex.tenantId !== tenantId) throw new ForbiddenException();
    return complex;
  }

  async getTree(id: string, tenantId: string | null, isSuperAdmin: boolean) {
    await this.findOne(id, tenantId, isSuperAdmin);

    const units = await this.prisma.spatialUnit.findMany({
      where: { complexId: id },
      include: {
        lots: {
          include: { residents: { include: { user: true } } },
        },
      },
      orderBy: [{ type: 'asc' }, { code: 'asc' }],
    });

    // build tree
    const byId: Record<string, any> = {};
    const roots: any[] = [];
    units.forEach((u) => {
      byId[u.id] = { ...u, children: [] };
    });
    units.forEach((u) => {
      if (u.parentId && byId[u.parentId]) {
        byId[u.parentId].children.push(byId[u.id]);
      } else {
        roots.push(byId[u.id]);
      }
    });
    return { complexId: id, tree: roots };
  }

  async addSpatialUnit(complexId: string, dto: CreateSpatialUnitDto) {
    const complex = await this.prisma.complex.findUnique({ where: { id: complexId } });
    if (!complex) throw new NotFoundException('Complex not found');

    let path = dto.code;
    if (dto.parentId) {
      const parent = await this.prisma.spatialUnit.findUnique({ where: { id: dto.parentId } });
      if (!parent || parent.complexId !== complexId) {
        throw new BadRequestException('Invalid parent unit');
      }
      path = parent.path ? `${parent.path}.${dto.code}` : `${parent.code}.${dto.code}`;
    }

    return this.prisma.spatialUnit.create({
      data: {
        complexId,
        parentId: dto.parentId,
        type: dto.type,
        code: dto.code,
        label: dto.label,
        path,
      },
    });
  }

  async addRepartitionKey(complexId: string, dto: CreateRepartitionKeyDto) {
    return this.prisma.repartitionKey.create({
      data: {
        complexId,
        code: dto.code,
        label: dto.label,
        type: dto.type,
        scopeSpatialUnitId: dto.scopeSpatialUnitId,
        description: dto.description,
      },
    });
  }

  // Dashboard KPIs (endpoint #15)
  async getDashboard(id: string, tenantId: string | null, isSuperAdmin: boolean) {
    await this.findOne(id, tenantId, isSuperAdmin);

    const [lotsCount, residentsCount, openTickets, activeFundCalls, collectionStats] =
      await Promise.all([
        this.prisma.lot.count({ where: { complexId: id } }),
        this.prisma.resident.count({ where: { lot: { complexId: id }, endDate: null } }),
        // ticketing module will come in Phase 2 — safe default for now
        Promise.resolve(0),
        this.prisma.fundCall.count({ where: { complexId: id, status: 'ISSUED' } }),
        this.prisma.fundCallItem.groupBy({
          by: ['status'],
          where: { fundCall: { complexId: id } },
          _sum: { amount: true, paidAmount: true },
          _count: true,
        }),
      ]);

    const totalBilled = collectionStats.reduce((a, s) => a + Number(s._sum.amount ?? 0), 0);
    const totalCollected = collectionStats.reduce((a, s) => a + Number(s._sum.paidAmount ?? 0), 0);
    const collectionRate = totalBilled > 0 ? (totalCollected / totalBilled) * 100 : 0;

    return {
      complexId: id,
      lotsCount,
      residentsCount,
      openTickets,
      activeFundCalls,
      finance: {
        totalBilled,
        totalCollected,
        collectionRate: Math.round(collectionRate * 100) / 100,
        breakdown: collectionStats,
      },
    };
  }

  /**
   * Generate the full spatial tree + lots in a single transaction.
   *
   * Example input: 2 buildings × 5 floors × 4 units + 50 parkings + 20 caves
   *   → 2 BATIMENT + 10 ETAGE + 40 lots APPARTEMENT + 1 ZONE "Parking" with 50 PARKING lots
   *     + 1 ZONE "Caves" with 20 CAVE lots.
   *
   * UX_RECOMMANDATIONS P0-6.
   */
  async generateLayout(
    complexId: string,
    tenantId: string | null,
    isSuperAdmin: boolean,
    dto: GenerateLayoutDto,
  ) {
    await this.findOne(complexId, tenantId, isSuperAdmin);

    const existing = await this.prisma.lot.count({ where: { complexId } });
    if (existing > 0) {
      throw new BadRequestException(
        'Layout generator can only run on empty complexes (aucun lot existant).',
      );
    }

    let createdLots = 0;
    let createdUnits = 0;

    await this.prisma.$transaction(
      async (tx) => {
        // Buildings
        for (const b of dto.buildings) {
          const building = await tx.spatialUnit.create({
            data: {
              complexId,
              type: SpatialUnitType.BATIMENT,
              code: b.code,
              label: b.label ?? `Bâtiment ${b.code}`,
              path: b.code,
            },
          });
          createdUnits++;

          for (let f = 1; f <= b.floors; f++) {
            const floor = await tx.spatialUnit.create({
              data: {
                complexId,
                parentId: building.id,
                type: SpatialUnitType.ETAGE,
                code: String(f),
                label: `Étage ${f}`,
                path: `${building.code}.${f}`,
              },
            });
            createdUnits++;

            const lotData: Prisma.LotCreateManyInput[] = [];
            for (let u = 1; u <= b.unitsPerFloor; u++) {
              const unitCode = String(u).padStart(2, '0');
              lotData.push({
                complexId,
                spatialUnitId: floor.id,
                lotNumber: `${b.code}-${f}${unitCode}`,
                type: b.unitType ?? LotType.APPARTEMENT,
                surfaceM2: b.defaultSurfaceM2 != null ? new Prisma.Decimal(b.defaultSurfaceM2) : null,
                tantiemesGeneral: 0,
              });
            }
            const res = await tx.lot.createMany({ data: lotData });
            createdLots += res.count;
          }
        }

        // Parkings zone
        if ((dto.parkingCount ?? 0) > 0) {
          const zone = await tx.spatialUnit.create({
            data: {
              complexId,
              type: SpatialUnitType.ZONE,
              code: 'PARK',
              label: 'Parkings',
              path: 'PARK',
            },
          });
          createdUnits++;
          const lotData: Prisma.LotCreateManyInput[] = [];
          for (let i = 1; i <= (dto.parkingCount ?? 0); i++) {
            lotData.push({
              complexId,
              spatialUnitId: zone.id,
              lotNumber: `P-${String(i).padStart(3, '0')}`,
              type: LotType.PARKING,
              tantiemesGeneral: 0,
            });
          }
          const res = await tx.lot.createMany({ data: lotData });
          createdLots += res.count;
        }

        // Caves zone
        if ((dto.caveCount ?? 0) > 0) {
          const zone = await tx.spatialUnit.create({
            data: {
              complexId,
              type: SpatialUnitType.ZONE,
              code: 'CAVE',
              label: 'Caves',
              path: 'CAVE',
            },
          });
          createdUnits++;
          const lotData: Prisma.LotCreateManyInput[] = [];
          for (let i = 1; i <= (dto.caveCount ?? 0); i++) {
            lotData.push({
              complexId,
              spatialUnitId: zone.id,
              lotNumber: `C-${String(i).padStart(3, '0')}`,
              type: LotType.CAVE,
              tantiemesGeneral: 0,
            });
          }
          const res = await tx.lot.createMany({ data: lotData });
          createdLots += res.count;
        }
      },
      { timeout: 30_000, maxWait: 5_000 },
    );

    return { complexId, createdUnits, createdLots };
  }

  async update(id: string, tenantId: string | null, isSuperAdmin: boolean, dto: CreateComplexDto) {
    await this.findOne(id, tenantId, isSuperAdmin);
    return this.prisma.complex.update({
      where: { id },
      data: {
        name: dto.name,
        type: dto.type,
        address: dto.address,
        city: dto.city,
        country: dto.country,
        gpsLat: dto.gpsLat,
        gpsLng: dto.gpsLng,
      },
    });
  }

  async remove(id: string, tenantId: string | null, isSuperAdmin: boolean) {
    await this.findOne(id, tenantId, isSuperAdmin);
    await this.prisma.complex.delete({ where: { id } });
    return { deleted: true };
  }
}
