import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { PrismaService } from '../../common/prisma.service';
import { CreateTravelerProfileDto, CreateSavedRouteDto, RegisterPassengerDto } from './passengers.dto';
import { VerifaydaService } from '../verifayda/verifayda.service';
import { DeleteOperationException } from '../../common/exceptions/delete-operation.exception';
import { AuditService } from '../../common/audit.service';

interface PassengerFilters {
  search?: string;
  gender?: string;
  dateFrom?: string;
  dateTo?: string;
  page?: number;
  pageSize?: number;
}

type IamUserRow = {
  id: string;
  email: string;
  name: { en: string; am: string } | null;
  phone_number: string | null;
  metadata: Record<string, any> | null;
  verified_by?: string | null;
};

@Injectable()
export class PassengersService {
  private readonly logger = new Logger(PassengersService.name);
  constructor(
    private readonly prisma: PrismaService,
    @InjectDataSource() private readonly dataSource: DataSource,
    private readonly verifaydaService: VerifaydaService,
    private readonly auditService: AuditService,
  ) {}

  async findAll(filters: PassengerFilters = {}) {
    const { search, gender, dateFrom, dateTo, page = 1, pageSize = 20 } = filters;
    const skip = (page - 1) * pageSize;

    const where: any = {};

    if (search) {
      // IAM user search: resolve matching iamUserIds first, then filter by passengerId
      const iamRows = await this.dataSource.query<{ id: string }[]>(
        `SELECT id FROM iam.users WHERE (name->>'en') ILIKE $1 OR (name->>'am') ILIKE $1 OR email ILIKE $1 OR phone_number ILIKE $1`,
        [`%${search}%`],
      );
      const matchedPassengers = iamRows.length > 0
        ? await this.prisma.passenger.findMany({ where: { iamUserId: { in: iamRows.map(r => r.id) } }, select: { id: true } })
        : [];

      where.OR = [
        { fullName: { contains: search, mode: 'insensitive' } },
        ...(matchedPassengers.length > 0 ? [{ passengerId: { in: matchedPassengers.map(p => p.id) } }] : []),
      ];
    }

    if (gender) {
      where.gender = gender;
    }

    if (dateFrom || dateTo) {
      where.createdAt = {
        ...(dateFrom ? { gte: new Date(dateFrom) } : {}),
        ...(dateTo ? { lte: new Date(new Date(dateTo).setHours(23, 59, 59, 999)) } : {}),
      };
    }

    const [items, total] = await Promise.all([
      this.prisma.travelerProfile.findMany({
        where,
        skip,
        take: pageSize,
        orderBy: { createdAt: 'desc' },
        include: {
          passenger: {
            include: {
              loyalty: true,
              wallet: true,
              _count: { select: { bookings: true } },
              bookings: {
                take: 1,
                orderBy: { createdAt: 'desc' },
                select: {
                  contactPhone: true,
                  contactEmail: true,
                  seats: {
                    take: 1,
                    orderBy: { id: 'asc' },
                    select: {
                      passengerName: true,
                      passportNumber: true,
                      passportCountry: true,
                    },
                  },
                },
              },
            },
          },
        },
      }),
      this.prisma.travelerProfile.count({ where }),
    ]);

    const iamUserIds = items.map(p => p.passenger?.iamUserId).filter(Boolean) as string[];
    const iamRows = iamUserIds.length > 0
      ? await this.dataSource.query<IamUserRow[]>(
          `SELECT id, email, name, phone_number, metadata FROM iam.users WHERE id = ANY($1)`,
          [iamUserIds],
        )
      : [];
    const iamMap = new Map(iamRows.map(r => [r.id, r]));

    return {
      items: items.map(profile => {
        const passenger = profile.passenger;
        const iam = passenger?.iamUserId ? iamMap.get(passenger.iamUserId) : undefined;
        const faydaVerified = iam?.metadata?.faydaVerified === true
          || iam?.metadata?.faydaVerified === 'true';
        
        const guestBooking = (passenger as any)?.bookings?.[0] ?? null;
        const guestSeat = guestBooking?.seats?.[0] ?? null;
        
        let notesData: any = null;
        if (profile.notes) {
          try {
            notesData = typeof profile.notes === 'string' ? JSON.parse(profile.notes) : profile.notes;
          } catch {
            notesData = null;
          }
        }
        
        return {
          id: profile.id,
          fullName: profile.fullName,
          email: iam?.email ?? notesData?.email ?? guestBooking?.contactEmail ?? null,
          phone: iam?.phone_number ?? notesData?.phone ?? guestBooking?.contactPhone ?? null,
          gender: profile.gender ?? iam?.metadata?.gender ?? null,
          dateOfBirth: profile.dateOfBirth
            ? new Date(profile.dateOfBirth).toISOString().split('T')[0]
            : (iam?.metadata?.dateOfBirth ?? null),
          nationality: iam?.metadata?.nationality ?? notesData?.nationality ?? (guestSeat?.passportCountry ? (guestSeat.passportCountry === 'Ethiopia' ? 'Ethiopian' : guestSeat.passportCountry) : null),
          nationalityCode: iam?.metadata?.nationalityCode ?? null,
          faydaVerified,
          faydaVerifiedAt: iam?.metadata?.faydaVerifiedAt ?? null,
          passportNumber: iam?.metadata?.passportNumber ?? notesData?.passportNumber ?? guestSeat?.passportNumber ?? null,
          passportCountry: iam?.metadata?.passportCountry ?? notesData?.passportCountry ?? guestSeat?.passportCountry ?? null,
          passportExpiryDate: iam?.metadata?.passportExpiryDate ?? null,
          idDocumentType: profile.nationalId ? 'NATIONAL_ID' : (notesData?.idDocumentType ?? null),
          verified: faydaVerified,
          lastLoginAt: null,
          role: null,
          loyalty: passenger?.loyalty
            ? { tier: passenger.loyalty.tier, pointsBalance: passenger.loyalty.pointsBalance, lifetimePoints: (passenger.loyalty as any).lifetimePoints ?? 0 }
            : null,
          wallet: (passenger as any)?.wallet
            ? { balanceMinor: (passenger as any).wallet.balanceMinor, currency: (passenger as any).wallet.currency ?? 'ETB' }
            : null,
          loyaltyTier: passenger?.loyalty?.tier || 'BRONZE',
          loyaltyPoints: passenger?.loyalty?.pointsBalance || 0,
          totalBookings: passenger?._count?.bookings || 0,
          createdAt: profile.createdAt,
        };
      }),
      meta: {
        page,
        pageSize,
        total,
        totalPages: Math.ceil(total / pageSize),
      },
    };
  }

  async getProfile(passengerId: string) {
    const passenger = await this.prisma.passenger.findUnique({
      where: { id: passengerId },
      include: {
        bookings: {
          orderBy: { createdAt: 'desc' },
          take: 10,
          include: {
            schedule: { include: { originStation: true, destinationStation: true, train: true } },
            seats: { include: { seat: { include: { coach: true } } } },
          },
        },
        loyalty: true,
        wallet: true,
        travelerProfiles: true,
        savedRoutes: true,
      },
    });
    if (!passenger) throw new NotFoundException('Passenger not found');

    let iamUser: IamUserRow | null = null;
    if (passenger.iamUserId) {
      const rows = await this.dataSource.query<IamUserRow[]>(
        `SELECT id, email, name, phone_number, metadata FROM iam.users WHERE id = $1 LIMIT 1`,
        [passenger.iamUserId],
      );
      iamUser = rows[0] ?? null;
    }

    return {
      id: passenger.id,
      fullName: iamUser?.name?.en ?? iamUser?.name?.am ?? null,
      email: iamUser?.email ?? null,
      phone: iamUser?.phone_number ?? null,
      createdAt: passenger.createdAt,
      bookings: passenger.bookings.map((b) => ({
        id: b.id,
        bookingRef: b.bookingRef,
        status: b.status,
        totalFare: b.totalMinor / 100,
        createdAt: b.createdAt,
        trip: {
          number: b.schedule.train.number,
          origin: { 
            id: b.schedule.originStation.id, 
            name: b.schedule.originStation.name, 
            code: b.schedule.originStation.code, 
            city: b.schedule.originStation.city 
          },
          destination: { 
            id: b.schedule.destinationStation.id, 
            name: b.schedule.destinationStation.name, 
            code: b.schedule.destinationStation.code, 
            city: b.schedule.destinationStation.city 
          },
          departureAt: b.schedule.departureAt,
        },
        passengers: b.seats.map((bs) => ({
          fullName: bs.passengerName,
          seat: { number: bs.seat.seatNumber, coach: bs.seat.coach.number, class: 'N/A' },
        })),
      })),
    };
  }

  /**
   * Full passenger-form payload for the logged-in user, sourced from the IAM user record
   * (iam.users) — where the Fayda-verified identity actually lives — rather than the sparse
   * Passenger row. The booking passenger form (/booking/passengers) calls this via
   * GET /passengers/me to prefill (and lock) an already-verified passenger's details.
   *
   * Identity fields don't depend on a Passenger row existing; only `id` (used later to tag the
   * primary passenger on the booking) does, and it's null if no Passenger row is linked yet.
   */
  async getMyProfile(iamUserId: string) {
    const [passenger, iamRows] = await Promise.all([
      this.prisma.passenger.findUnique({ where: { iamUserId } }),
      this.dataSource.query<IamUserRow[]>(
        `SELECT id, email, name, phone_number, metadata, verified_by FROM iam.users WHERE id = $1 LIMIT 1`,
        [iamUserId],
      ),
    ]);

    const iam = iamRows[0] ?? null;
    if (!iam && !passenger) return null;

    const meta = iam?.metadata ?? {};
    const faydaVerified =
      iam?.verified_by === 'fayda' ||
      meta.faydaVerified === true ||
      meta.faydaVerified === 'true';

    // Fayda writes gender as { am, en }; older/manual records may store a plain string.
    const gender =
      meta.gender && typeof meta.gender === 'object'
        ? (meta.gender.en ?? meta.gender.am ?? null)
        : (meta.gender ?? null);

    const fullName = iam?.name?.en ?? iam?.name?.am ?? null;

    // Stored as ISO by the Fayda upsert; tolerate a "/"-separated legacy value.
    const rawDob = meta.dateOfBirth ?? meta.birthdate ?? null;
    const dateOfBirth = rawDob ? String(rawDob).replace(/\//g, '-') : null;

    // Nationality isn't always in metadata; a Fayda-verified holder is Ethiopian by definition.
    const nationality = meta.nationality ?? (faydaVerified ? 'ETHIOPIAN' : null);

    return {
      id: passenger?.id ?? null,
      fullName,
      email: iam?.email ?? meta.email ?? null,
      phone: iam?.phone_number ?? meta.phoneNumber ?? null,
      gender,
      dateOfBirth,
      nationality,
      faydaVerified,
      faydaSub: meta.sub ?? null,
      passportNumber: meta.passportNumber ?? null,
      passportCountry: meta.passportCountry ?? null,
      passportIssueDate: meta.passportIssueDate ?? null,
      passportExpiryDate: meta.passportExpiryDate ?? null,
      passportIssuingAuthority: meta.passportIssuingAuthority ?? null,
    };
  }

  async getStats(passengerId: string) {
    const [totalTrips, totalSpendResult, loyalty] = await Promise.all([
      this.prisma.booking.count({ where: { passengerId, status: 'BOARDED' as any } }),
      this.prisma.booking.aggregate({ where: { passengerId, status: 'BOARDED' as any }, _sum: { totalMinor: true } }),
      this.prisma.loyaltyAccount.findUnique({ where: { passengerId } }),
    ]);
    const totalSpend = ((totalSpendResult._sum?.totalMinor ?? 0) as number) / 100;
    return { totalTrips, totalSpend, loyaltyPoints: loyalty?.pointsBalance ?? 0, co2Saved: totalTrips * 6 };
  }

  async savePassengers(passengers: any[], userId?: string, deviceId?: string) {
    if (!passengers || !Array.isArray(passengers)) {
      throw new BadRequestException('Passengers array is required');
    }

    if (passengers.length === 0) {
      throw new BadRequestException('At least one passenger is required');
    }

    const savedProfiles = await Promise.all(
      passengers.map((p) =>
        this.prisma.savedPassengerProfile.create({
          data: {
            userId,
            deviceId,
            passengerName: p.name || p.passengerName,
            dateOfBirth: new Date(p.dateOfBirth),
            idDocumentType: p.nationalId ? 'NATIONAL_ID' : 'PASSPORT',
            passportNumber: p.passportNumber,
            passportCountry: p.passportCountry,
            nationality: p.nationality,
            phone: p.phone,
            email: p.email,
          },
        })
      )
    );
    return {
      count: savedProfiles.length,
      passengerIds: savedProfiles.map(p => p.id),
      passengers: savedProfiles.map(p => ({
        id: p.id,
        passengerName: p.passengerName,
        dateOfBirth: p.dateOfBirth,
        nationality: p.nationality,
        phone: p.phone ?? null,
        email: p.email ?? null,
      })),
      message: 'Passenger details saved successfully',
    };
  }

  createTravelerProfile(dto: CreateTravelerProfileDto) {
    return this.prisma.travelerProfile.create({ 
      data: { 
        passengerId: dto.passengerId,
        fullName: dto.fullName,
        relationship: dto.relationship,
        dateOfBirth: dto.dateOfBirth ? new Date(dto.dateOfBirth) : null,
        nationalId: dto.nationalId || null,
        gender: dto.gender || null,
        notes: dto.notes || null,
      } 
    });
  }

  getTravelerProfiles(passengerId: string) { 
    return this.prisma.travelerProfile.findMany({ where: { passengerId } }); 
  }

  createSavedRoute(dto: CreateSavedRouteDto) { 
    return this.prisma.savedRoute.create({ data: dto }); 
  }

  getSavedRoutes(passengerId: string) { 
    return this.prisma.savedRoute.findMany({ where: { passengerId }, orderBy: { tripCount: 'desc' } }); 
  }

  async updatePassenger(id: string, dto: any) {
    const passenger = await this.prisma.passenger.findUnique({ where: { id } });
    if (!passenger) throw new NotFoundException('Passenger not found');

    if (passenger.iamUserId && (dto.fullName || dto.email || dto.phone)) {
      const updates: string[] = [];
      const params: any[] = [];
      let idx = 1;

      if (dto.fullName) {
        updates.push(`name = COALESCE(name, '{}') || jsonb_build_object('en', $${idx}::text, 'am', $${idx}::text)`);
        params.push(dto.fullName);
        idx++;
      }
      if (dto.email) {
        updates.push(`email = $${idx}`);
        params.push(dto.email);
        idx++;
      }
      if (dto.phone) {
        updates.push(`phone_number = $${idx}`);
        params.push(dto.phone);
        idx++;
      }

      params.push(passenger.iamUserId);
      await this.dataSource.query(
        `UPDATE iam.users SET ${updates.join(', ')} WHERE id = $${idx}`,
        params,
      );
    }

    const [updated, iamRows] = await Promise.all([
      this.prisma.passenger.findUnique({ where: { id }, include: { loyalty: true } }),
      passenger.iamUserId
        ? this.dataSource.query<IamUserRow[]>(
            `SELECT id, email, name, phone_number, metadata FROM iam.users WHERE id = $1 LIMIT 1`,
            [passenger.iamUserId],
          )
        : Promise.resolve([] as IamUserRow[]),
    ]);

    const iamUser = iamRows[0] ?? null;
    return {
      id: updated!.id,
      fullName: iamUser?.name?.en ?? iamUser?.name?.am ?? null,
      email: iamUser?.email ?? null,
      phone: iamUser?.phone_number ?? null,
      loyalty: updated!.loyalty,
    };
  }

  async registerPassenger(dto: RegisterPassengerDto) {
    const isEthiopian = !!dto.nationalId;
    const isLoggedIn = !!dto.userId;
    let verifiedData: any = null;

    if (isEthiopian && dto.verifyWithFayda !== false) {
      try {
        const verification = await this.verifaydaService.verifyNationalId(dto.nationalId!);
        if (verification.verified && verification.passengerData) {
          verifiedData = verification.passengerData;
        }
      } catch (error) {
        this.logger.warn('Fayda verification failed, using manual data:', error);
      }
    }

    const finalData = {
      passengerName: verifiedData?.fullName || dto.passengerName,
      dateOfBirth: verifiedData?.dateOfBirth || new Date(dto.dateOfBirth),
      nationality: verifiedData?.nationality || dto.nationality || (isEthiopian ? 'Ethiopian' : null),
      gender: verifiedData?.gender || dto.gender,
      phone: dto.phone,
      email: dto.email,
    };

    if (isLoggedIn) {
      const linkedPassenger = await this.prisma.passenger.findUnique({
        where: { iamUserId: dto.userId },
      });

      if (!linkedPassenger) {
        throw new BadRequestException('Passenger not found');
      }

      return {
        id: linkedPassenger.id,
        passengerName: finalData.passengerName,
        dateOfBirth: finalData.dateOfBirth,
        nationality: finalData.nationality,
        verified: !!verifiedData,
        linked: true,
        message: 'Passenger details saved and linked to user account',
      };
    }

    const profile = await this.prisma.savedPassengerProfile.create({
      data: {
        deviceId: dto.deviceId,
        passengerName: finalData.passengerName,
        dateOfBirth: finalData.dateOfBirth,
        idDocumentType: isEthiopian ? 'NATIONAL_ID' : 'PASSPORT',
        passportNumber: dto.passportNumber,
        passportCountry: dto.passportCountry,
        nationality: finalData.nationality,
        phone: dto.phone,
        email: dto.email,
      },
    });

    return {
      id: profile.id,
      passengerName: finalData.passengerName,
      dateOfBirth: finalData.dateOfBirth,
      nationality: finalData.nationality,
      verified: !!verifiedData,
      linked: false,
      message: 'Passenger details saved for guest booking',
    };
  }

  async deletePassenger(id: string, cascade = false) {
    // id may be a TravelerProfile.id (from the list endpoint) or a Passenger.id
    let passenger = await this.prisma.passenger.findUnique({ where: { id } });

    if (!passenger) {
      const profile = await this.prisma.travelerProfile.findUnique({ where: { id } });
      if (!profile?.passengerId) throw new NotFoundException('Passenger not found');
      passenger = await this.prisma.passenger.findUnique({ where: { id: profile.passengerId } });
      if (!passenger) throw new NotFoundException('Passenger not found');
    }

    const passengerId = passenger.id;

    if (!cascade) {
      const usage = await this.checkPassengerUsage(passengerId);
      if (usage.isInUse && usage.constraints) {
        const passengerName = `Passenger ${passengerId.slice(-8)}`;
        throw new DeleteOperationException('Passenger', passengerName, usage.constraints);
      }
    }

    // Resolve booking IDs first (needed for multi-step child deletion)
    const bookings = await this.prisma.booking.findMany({
      where: { passengerId },
      select: { id: true },
    });
    const bookingIds = bookings.map(b => b.id);

    if (bookingIds.length > 0) {
      const tickets = await this.prisma.ticket.findMany({ where: { bookingId: { in: bookingIds } }, select: { id: true } });
      if (tickets.length > 0) {
        await this.prisma.gateValidationLog.deleteMany({ where: { ticketId: { in: tickets.map(t => t.id) } } });
      }
      await this.prisma.ticket.deleteMany({ where: { bookingId: { in: bookingIds } } });

      const foodOrders = await this.prisma.foodOrder.findMany({ where: { bookingId: { in: bookingIds } }, select: { id: true } });
      if (foodOrders.length > 0) {
        await this.prisma.foodOrderItem.deleteMany({ where: { orderId: { in: foodOrders.map(o => o.id) } } });
      }
      await this.prisma.foodOrder.deleteMany({ where: { bookingId: { in: bookingIds } } });

      const paymentIntents = await this.prisma.paymentIntent.findMany({ where: { bookingId: { in: bookingIds } }, select: { id: true } });
      if (paymentIntents.length > 0) {
        await this.prisma.paymentRefund.deleteMany({ where: { paymentIntentId: { in: paymentIntents.map(p => p.id) } } });
      }
      await this.prisma.paymentIntent.deleteMany({ where: { bookingId: { in: bookingIds } } });

      await this.prisma.bookingSeat.deleteMany({ where: { bookingId: { in: bookingIds } } });
      await this.prisma.agentBooking.deleteMany({ where: { bookingId: { in: bookingIds } } });
      await this.prisma.bookingModification.deleteMany({ where: { bookingId: { in: bookingIds } } });
      await this.prisma.bookingCancellation.deleteMany({ where: { bookingId: { in: bookingIds } } });
      await this.prisma.baggageBooking.deleteMany({ where: { bookingId: { in: bookingIds } } });
      await this.prisma.excessBaggageCharge.deleteMany({ where: { bookingId: { in: bookingIds } } });
      await this.prisma.journey.deleteMany({ where: { bookingId: { in: bookingIds } } });
      await this.prisma.booking.deleteMany({ where: { id: { in: bookingIds } } });
    }

    // Package bookings
    const packageBookings = await this.prisma.packageBooking.findMany({ where: { passengerId }, select: { id: true } });
    if (packageBookings.length > 0) {
      const pbIds = packageBookings.map(pb => pb.id);
      await this.prisma.packageBookingPassenger.deleteMany({ where: { bookingId: { in: pbIds } } });
      await this.prisma.packagePaymentIntent.deleteMany({ where: { packageBookingId: { in: pbIds } } });
      await this.prisma.packageBooking.deleteMany({ where: { id: { in: pbIds } } });
    }

    await this.prisma.loyaltyLedgerEntry.deleteMany({ where: { account: { passengerId } } });
    await this.prisma.loyaltyAccount.deleteMany({ where: { passengerId } });
    await this.prisma.walletLedgerEntry.deleteMany({ where: { wallet: { passengerId } } });
    await this.prisma.walletAccount.deleteMany({ where: { passengerId } });
    await this.prisma.notification.deleteMany({ where: { passengerId } });
    await this.prisma.travelerProfile.deleteMany({ where: { passengerId } });
    await this.prisma.savedRoute.deleteMany({ where: { passengerId } });
    await this.prisma.passenger.delete({ where: { id: passengerId } });
    await this.auditService.log({ action: 'DELETE', entityType: 'Passenger', entityId: passengerId });
    return { deleted: true, passengerId };
  }

  async checkPassengerUsage(id: string) {
    const [bookingCount, loyaltyAccount, walletAccount] = await Promise.all([
      this.prisma.booking.count({ where: { passengerId: id } }),
      this.prisma.loyaltyAccount.findUnique({ where: { passengerId: id } }),
      this.prisma.walletAccount.findUnique({ where: { passengerId: id } }),
    ]);

    const constraints = [];
    if (bookingCount > 0) constraints.push({ entityName: 'booking', count: bookingCount, action: 'complete' as const });
    if (loyaltyAccount) constraints.push({ entityName: 'loyalty account', count: 1, action: 'delete' as const });
    if (walletAccount) constraints.push({ entityName: 'wallet account', count: 1, action: 'delete' as const });

    return {
      isInUse: constraints.length > 0,
      affectedModules: constraints.map(c => `${c.count} ${c.entityName}${c.count > 1 ? 's' : ''}`),
      constraints
    };
  }
}
