import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { PrismaService } from '../../common/prisma.service';
import { CreateAgentBookingDto, OpenShiftDto, CloseShiftDto } from './agents.dto';
import { IdDocumentType } from '@prisma/client';

function generateRef(): string {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  return Array.from({ length: 6 }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}

@Injectable()
export class AgentsService {
  constructor(
    private prisma: PrismaService,
    @InjectDataSource() private readonly dataSource: DataSource,
  ) {}

  async findAll(filters: { search?: string; active?: string }) {
    const where: any = {};
    if (filters.active !== undefined && filters.active !== '') {
      where.active = filters.active === 'true';
    }
    const agents = await this.prisma.agent.findMany({
      where,
      orderBy: { createdAt: 'desc' },
    });
    // Enrich with IAM user data
    const iamUserIds = agents.map(a => a.iamUserId).filter(Boolean) as string[];
    type IamRow = { id: string; email: string; name: any; phone_number: string | null };
    const iamRows: IamRow[] = iamUserIds.length > 0
      ? await this.dataSource.query<IamRow[]>(
          `SELECT id, email, name, phone_number FROM iam.users WHERE id = ANY($1)`,
          [iamUserIds],
        ).catch(() => [])
      : [];
    const iamMap = new Map(iamRows.map(r => [r.id, r]));
    const items = agents
      .map(a => {
        const iam = a.iamUserId ? iamMap.get(a.iamUserId) ?? null : null;
        const fullName = iam?.name?.en ?? iam?.name?.am ?? null;
        if (filters.search) {
          const q = filters.search.toLowerCase();
          const matches = a.agentCode.toLowerCase().includes(q)
            || (iam?.email ?? '').toLowerCase().includes(q)
            || (fullName ?? '').toLowerCase().includes(q);
          if (!matches) return null;
        }
        return {
          ...a,
          user: iam ? { fullName, email: iam.email, phone: iam.phone_number } : null,
        };
      })
      .filter(Boolean);
    return { items, total: items.length };
  }

  async createAgentBooking(dto: CreateAgentBookingDto) {
    const agent = await this.prisma.agent.findUnique({ where: { id: dto.agentId } });
    if (!agent || !agent.active) throw new NotFoundException('Agent not found or inactive');

    const passenger = agent.iamUserId
      ? await this.prisma.passenger.findUnique({ where: { iamUserId: agent.iamUserId } })
      : null;
    if (!passenger) throw new BadRequestException('Agent must have a linked passenger account');

    const schedule = await this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId } });
    if (!schedule) throw new NotFoundException('Schedule not found');

    const seatIds = dto.passengers.map(p => p.seatId);
    const seats = await this.prisma.seat.findMany({ where: { id: { in: seatIds } } });
    if (seats.length !== seatIds.length) throw new BadRequestException('Invalid seat selection');

    const baseFare = 45000 * dto.passengers.length;
    const totalMinor = baseFare;

    const booking = await this.prisma.booking.create({
      data: {
        bookingRef: generateRef(),
        passengerId: passenger.id,
        scheduleId: dto.scheduleId,
        status: dto.paymentMethod === 'CASH' ? 'CONFIRMED' : 'PENDING_PAYMENT',
        totalMinor,
        seats: {
          create: dto.passengers.map(p => ({
            seat: { connect: { id: p.seatId } },
            scheduleId: dto.scheduleId,
            passengerName: p.fullName,
            idDocumentType: p.idDocumentType as IdDocumentType | undefined,
            idDocumentNumber: p.idDocumentNumber
          }))
        }
      },
      include: { seats: true }
    });

    await this.prisma.seat.updateMany({
      where: { id: { in: seatIds } },
      data: { status: 'BOOKED' }
    });

    const changeGiven = dto.cashReceived ? dto.cashReceived - totalMinor : 0;
    await this.prisma.agentBooking.create({
      data: {
        agentId: dto.agentId,
        bookingId: booking.id,
        paymentMethod: dto.paymentMethod,
        cashReceived: dto.cashReceived,
        changeGiven,
        paperTicket: dto.paperTicket ?? false
      }
    });

    const commissionAmount = Math.floor(totalMinor * agent.commissionRate / 100);
    await this.prisma.agentCommission.create({
      data: {
        agentId: dto.agentId,
        bookingId: booking.id,
        amountMinor: commissionAmount,
        rate: agent.commissionRate
      }
    });

    return { booking, commission: commissionAmount };
  }

  async openShift(dto: OpenShiftDto) {
    const agent = await this.prisma.agent.findUnique({ where: { id: dto.agentId } });
    if (!agent) throw new NotFoundException('Agent not found');

    const openShift = await this.prisma.agentShift.findFirst({
      where: { agentId: dto.agentId, closedAt: null }
    });
    if (openShift) throw new BadRequestException('Shift already open');

    return this.prisma.agentShift.create({
      data: {
        agentId: dto.agentId,
        openingBalance: dto.openingBalance ?? 0
      }
    });
  }

  async closeShift(dto: CloseShiftDto) {
    const shift = await this.prisma.agentShift.findUnique({ where: { id: dto.shiftId } });
    if (!shift) throw new NotFoundException('Shift not found');
    if (shift.closedAt) throw new BadRequestException('Shift already closed');

    return this.prisma.agentShift.update({
      where: { id: dto.shiftId },
      data: {
        closedAt: new Date(),
        closingBalance: dto.closingBalance,
        notes: dto.notes,
        reconciled: true
      }
    });
  }

  async getCommissions(agentId: string, dateFrom?: Date, dateTo?: Date) {
    return this.prisma.agentCommission.findMany({
      where: {
        agentId,
        createdAt: {
          gte: dateFrom,
          lte: dateTo
        }
      },
      orderBy: { createdAt: 'desc' }
    });
  }

  async getShifts(agentId: string) {
    return this.prisma.agentShift.findMany({
      where: { agentId },
      orderBy: { openedAt: 'desc' },
      take: 20
    });
  }

  async getMe(iamUserId: string) {
    const agent = await this.prisma.agent.findUnique({ where: { iamUserId } });
    if (!agent) throw new NotFoundException('No agent profile found for this user');
    return agent;
  }

  async createAgent(dto: { iamUserId: string; agentCode?: string; commissionRate?: number }) {
    const existing = await this.prisma.agent.findUnique({ where: { iamUserId: dto.iamUserId } });
    if (existing) throw new BadRequestException('An agent profile already exists for this user');
    const agentCode = dto.agentCode || `AG${String(Date.now()).slice(-4)}`;
    return this.prisma.agent.create({
      data: {
        iamUserId: dto.iamUserId,
        agentCode,
        commissionRate: dto.commissionRate ?? 5,
        active: true,
      },
    });
  }

  async updateAgent(id: string, dto: { agentCode?: string; commissionRate?: number; active?: boolean }) {
    const agent = await this.prisma.agent.findUnique({ where: { id } });
    if (!agent) throw new NotFoundException('Agent not found');
    return this.prisma.agent.update({
      where: { id },
      data: {
        ...(dto.agentCode !== undefined && { agentCode: dto.agentCode }),
        ...(dto.commissionRate !== undefined && { commissionRate: dto.commissionRate }),
        ...(dto.active !== undefined && { active: dto.active }),
      },
    });
  }

  async deleteAgent(id: string) {
    const agent = await this.prisma.agent.findUnique({ where: { id } });
    if (!agent) throw new NotFoundException('Agent not found');
    await this.prisma.agent.delete({ where: { id } });
    return { deleted: true };
  }
}
