import {
  BadRequestException,
  Injectable,
  Logger,
  NotFoundException,
} from '@nestjs/common';
import { parse } from 'csv-parse/sync';
import { ImportKind, ImportStatus, LotType, ResidentRole } from '@prisma/client';
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
import { PrismaService } from '../../prisma/prisma.service';
import { AuthenticatedUser } from '../../common/decorators/current-user.decorator';

interface LotRow {
  complexId: string;
  spatialUnitCode: string;
  lotNumber: string;
  type?: string;
  surfaceM2?: string;
  tantiemesGeneral?: string;
}

interface ResidentRow {
  complexId: string;
  lotNumber: string;
  email: string;
  firstName?: string;
  lastName?: string;
  phone?: string;
  role?: string;
}

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

  constructor(private readonly prisma: PrismaService) {}

  async import(
    user: AuthenticatedUser,
    kind: ImportKind,
    filename: string,
    csvContent: string,
  ) {
    if (!user.tenantId) throw new BadRequestException('Tenant required');

    const job = await this.prisma.importJob.create({
      data: {
        tenantId: user.tenantId,
        userId: user.userId,
        kind,
        filename,
        status: ImportStatus.PROCESSING,
      },
    });

    let rows: any[];
    try {
      rows = parse(csvContent, {
        columns: true,
        skip_empty_lines: true,
        trim: true,
      });
    } catch (err) {
      return this.prisma.importJob.update({
        where: { id: job.id },
        data: {
          status: ImportStatus.FAILED,
          errors: {
            create: [{ rowNumber: 0, message: `CSV parse error: ${(err as Error).message}` }],
          },
        },
      });
    }

    let success = 0;
    const errors: { rowNumber: number; message: string; rawRow: any }[] = [];

    for (let i = 0; i < rows.length; i++) {
      const row = rows[i];
      try {
        if (kind === ImportKind.LOTS) await this.importLot(user.tenantId, row as LotRow);
        else if (kind === ImportKind.RESIDENTS) await this.importResident(user.tenantId, row as ResidentRow);
        else throw new Error(`Unsupported kind ${kind}`);
        success++;
      } catch (err) {
        errors.push({
          rowNumber: i + 2, // header = 1
          message: (err as Error).message,
          rawRow: row,
        });
      }
    }

    const finalStatus =
      errors.length === 0
        ? ImportStatus.SUCCEEDED
        : success === 0
        ? ImportStatus.FAILED
        : ImportStatus.PARTIAL;

    return this.prisma.importJob.update({
      where: { id: job.id },
      data: {
        status: finalStatus,
        totalRows: rows.length,
        successRows: success,
        errorRows: errors.length,
        errors: { create: errors },
      },
      include: { errors: true },
    });
  }

  private async importLot(tenantId: string, row: LotRow) {
    if (!row.complexId || !row.spatialUnitCode || !row.lotNumber) {
      throw new Error('Missing required fields: complexId, spatialUnitCode, lotNumber');
    }
    const complex = await this.prisma.complex.findFirst({
      where: { id: row.complexId, tenantId },
    });
    if (!complex) throw new Error(`Complex ${row.complexId} not found in tenant`);

    const spatialUnit = await this.prisma.spatialUnit.findFirst({
      where: { complexId: row.complexId, code: row.spatialUnitCode },
    });
    if (!spatialUnit) throw new Error(`Spatial unit ${row.spatialUnitCode} not found`);

    const lotType = (row.type as LotType) || LotType.APPARTEMENT;
    if (!Object.values(LotType).includes(lotType)) {
      throw new Error(`Invalid lot type: ${row.type}`);
    }

    await this.prisma.lot.upsert({
      where: { complexId_lotNumber: { complexId: row.complexId, lotNumber: row.lotNumber } },
      create: {
        complexId: row.complexId,
        spatialUnitId: spatialUnit.id,
        lotNumber: row.lotNumber,
        type: lotType,
        surfaceM2: row.surfaceM2 ? Number(row.surfaceM2) : null,
        tantiemesGeneral: row.tantiemesGeneral ? Number(row.tantiemesGeneral) : 0,
      },
      update: {
        type: lotType,
        surfaceM2: row.surfaceM2 ? Number(row.surfaceM2) : null,
        tantiemesGeneral: row.tantiemesGeneral ? Number(row.tantiemesGeneral) : 0,
      },
    });
  }

  private async importResident(tenantId: string, row: ResidentRow) {
    if (!row.complexId || !row.lotNumber || !row.email) {
      throw new Error('Missing required fields: complexId, lotNumber, email');
    }

    const lot = await this.prisma.lot.findUnique({
      where: { complexId_lotNumber: { complexId: row.complexId, lotNumber: row.lotNumber } },
    });
    if (!lot) throw new Error(`Lot ${row.lotNumber} not found`);

    const role = (row.role as ResidentRole) || ResidentRole.PROPRIETAIRE;
    if (!Object.values(ResidentRole).includes(role)) {
      throw new Error(`Invalid role: ${row.role}`);
    }

    const tempPassword = crypto.randomBytes(12).toString('base64url');
    const user = await this.prisma.user.upsert({
      where: { email: row.email },
      create: {
        email: row.email,
        firstName: row.firstName,
        lastName: row.lastName,
        phone: row.phone,
        passwordHash: await bcrypt.hash(tempPassword, 10),
        status: 'INVITED',
      },
      update: {
        firstName: row.firstName ?? undefined,
        lastName: row.lastName ?? undefined,
        phone: row.phone ?? undefined,
      },
    });

    // Attach role COPROPRIETAIRE/LOCATAIRE scoped to tenant
    const roleCode = role === ResidentRole.LOCATAIRE ? 'LOCATAIRE' : 'COPROPRIETAIRE';
    const roleRecord = await this.prisma.role.findUnique({ where: { code: roleCode } });
    if (roleRecord) {
      await this.prisma.userRole.upsert({
        where: {
          userId_roleId_tenantId_scopeComplexId: {
            userId: user.id,
            roleId: roleRecord.id,
            tenantId,
            scopeComplexId: row.complexId,
          },
        },
        create: {
          userId: user.id,
          roleId: roleRecord.id,
          tenantId,
          scopeComplexId: row.complexId,
        },
        update: {},
      });
    }

    await this.prisma.resident.upsert({
      where: {
        userId_lotId_role: { userId: user.id, lotId: lot.id, role },
      },
      create: { userId: user.id, lotId: lot.id, role },
      update: {},
    });
  }

  list(user: AuthenticatedUser) {
    if (!user.tenantId && !user.isSuperAdmin) return [];
    return this.prisma.importJob.findMany({
      where: user.isSuperAdmin ? {} : { tenantId: user.tenantId! },
      orderBy: { createdAt: 'desc' },
      include: { _count: { select: { errors: true } } },
      take: 50,
    });
  }

  async findOne(user: AuthenticatedUser, id: string) {
    const job = await this.prisma.importJob.findUnique({
      where: { id },
      include: { errors: { take: 100 } },
    });
    if (!job) throw new NotFoundException('Import job not found');
    if (!user.isSuperAdmin && job.tenantId !== user.tenantId) {
      throw new NotFoundException('Import job not found');
    }
    return job;
  }
}
