import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { CreateExpenseDto } from './dto/create-expense.dto';
import { UpdateExpenseDto } from './dto/update-expense.dto';
import { Expense, ExpenseStatus } from '@/entities/expense.entity';
import { ExpenseParticipant } from '@/entities/expense-participants.entity';
import { GroupRole } from '@/entities/group-member.entity';
import { NotificationService } from '@/notification/notification.service';
import { NotificationHelper } from '@/helpers/notification.helper';
import { User } from '@/entities/user.entity';
import { Trip } from '@/entities/trip.entity';

@Injectable()
export class ExpenseService {
  constructor(
    private dataSource: DataSource,

    @InjectRepository(Expense)
    private expenseRepo: Repository<Expense>,
    private notificationService: NotificationService,
  ) {}

  async create(trip: Trip, dto: CreateExpenseDto, req: any) {
    // 1. CHẶN NGÀY: Kiểm tra xem thời gian chi tiêu có hợp lệ với chuyến đi không
    const expenseTime = new Date(dto.time);
    const tripStart = new Date(trip.startDate);
    const tripEnd = new Date(trip.endDate);

    // Đặt thời gian của ngày bắt đầu về 00:00:00 để không bỏ sót chi tiêu đầu ngày
    tripStart.setHours(0, 0, 0, 0);
    // Đặt thời gian của ngày kết thúc về 23:59:59 để không bỏ sót chi tiêu cuối ngày
    tripEnd.setHours(23, 59, 59, 999);

    if (expenseTime < tripStart || expenseTime > tripEnd) {
      throw new BadRequestException(
        `Thời gian chi tiêu phải nằm trong khoảng thời gian diễn ra chuyến đi (${trip.startDate} đến ${trip.endDate}).`,
      );
    }

    return this.dataSource.transaction(async (manager) => {
      const expenseRepo = manager.getRepository(Expense);
      const participantRepo = manager.getRepository(ExpenseParticipant);

      const totalAmount = dto.amount;
      const count = dto.participants?.length || 0;

      // 2. Chặn số lượng người tham gia
      if (count < 2) {
        throw new BadRequestException(
          'Số người tham gia chia tiền phải từ 2 người trở lên.',
        );
      }

      // 3. Chặn số tiền chi tiêu hợp lệ
      if (totalAmount <= 0) {
        throw new BadRequestException('Số tiền chi tiêu phải lớn hơn 0.');
      }

      // 4. Chặn trường hợp tiền quá nhỏ không thể làm tròn chia đều
      const splitAmount = Math.round(totalAmount / count / 1000) * 1000;
      if (splitAmount === 0 && totalAmount > 0) {
        throw new BadRequestException(
          'Số tiền quá nhỏ để thực hiện chia đều theo đơn vị nghìn đồng.',
        );
      }

      const expense = expenseRepo.create({
        title: dto.title,
        category: dto.category,
        note: dto.note,
        amount: totalAmount,
        time: dto.time,
        status:
          req.userRole === GroupRole.OWNER
            ? ExpenseStatus.APPROVED
            : ExpenseStatus.PENDING,
        trip: { id: trip.id },
        paidBy: { id: dto.paidBy },
        createdBy: { id: req.user.id },
      });

      // Lưu bằng manager của transaction
      const savedExpense = await manager.save(Expense, expense);

      // 5. Logic tính toán và bù trừ phần dư
      const totalSplit = splitAmount * count;
      const remainder = totalAmount - totalSplit;

      const participants = dto.participants.map((userId, index) => {
        const finalAmount = index === 0 ? splitAmount + remainder : splitAmount;
        
        return participantRepo.create({
          expense: { id: savedExpense.id },
          user: { id: userId },
          amount: finalAmount,
        });
      });

      await manager.save(ExpenseParticipant, participants);

      // 6. Gửi thông báo nếu không phải OWNER
      if (req.userRole !== GroupRole.OWNER) {
        await this.addNotification(trip, savedExpense);
      }

      return savedExpense;
    });
  }

  async findByTrip(tripId: string) {
    const expenses = await this.expenseRepo.find({
      where: { trip: { id: tripId } },
      relations: ['paidBy', 'createdBy', 'participants', 'participants.user'],
      order: { createdAt: 'DESC' },
    });

    return expenses.map((item: Expense) => {
      return {
        ...item,
        paidBy: {
          id: item.paidBy.id,
          name: item.paidBy.name,
          avatar: item.paidBy.avatar,
          bank: item.paidBy.bank,
          bankAccNumber: item.paidBy.bankAccNumber
        },
        createdBy: {
          id: item.createdBy.id,
          name: item.createdBy.name,
          avatar: item.createdBy.avatar,
          bank: item.createdBy.bank,
          bankAccNumber: item.createdBy.bankAccNumber
        },
        participants: item.participants.map(
          (participant: ExpenseParticipant) => {
            return {
              id: participant.user?.id,
              name: participant.user?.name,
              avatar: participant.user?.avatar,
              bank: participant.user?.bank,
              bankAccNumber: participant.user?.bankAccNumber
            };
          },
        ),
      };
    });
  }

  async findOne(id: string) {
    const expense = await this.expenseRepo.findOne({
      where: { id },
      relations: ['paidBy', 'participants', 'participants.user'],
    });

    if (!expense) throw new NotFoundException('Expense not found');
    return expense;
  }

  async update(
    trip: Trip,
    id: string,
    dto: UpdateExpenseDto,
    userId: string,
    req: any
  ) {
    return this.dataSource.transaction(async (manager) => {
      const expenseRepo = manager.getRepository(Expense);
      const participantRepo = manager.getRepository(ExpenseParticipant);

      // Sử dụng manager để find nhằm đảm bảo đồng bộ dữ liệu trong transaction
      const expense = await expenseRepo.findOne({
        where: { id },
        relations: ['participants', 'createdBy', 'paidBy'],
      });

      if (!expense) {
        throw new NotFoundException('Không tìm thấy chi phí');
      }

      // Kiểm tra quyền chỉnh sửa
      if (expense.createdBy.id !== userId && expense.paidBy.id !== userId) {
        throw new ForbiddenException('Không có quyền sửa chi phí');
      }

      // 1. CHẶN NGÀY: Kiểm tra thời gian cập nhật có hợp lệ với chuyến đi không
      const expenseTime = new Date(dto.time ?? expense.time);
      const tripStart = new Date(trip.startDate);
      const tripEnd = new Date(trip.endDate);

      tripStart.setHours(0, 0, 0, 0);
      tripEnd.setHours(23, 59, 59, 999);

      if (expenseTime < tripStart || expenseTime > tripEnd) {
        throw new BadRequestException(
          `Thời gian chi tiêu phải nằm trong khoảng thời gian diễn ra chuyến đi (${trip.startDate} đến ${trip.endDate}).`,
        );
      }

      const totalAmount = dto.amount ?? expense.amount;
      const count = dto.participants?.length ?? 0;

      // 2. Chặn số lượng người tham gia
      if (count < 2) {
        throw new BadRequestException('Số người tham gia phải từ 2 trở lên');
      }

      // 3. Chặn số tiền chi tiêu hợp lệ
      if (totalAmount <= 0) {
        throw new BadRequestException('Số tiền chi tiêu phải lớn hơn 0.');
      }

      // 4. Chặn trường hợp tiền quá nhỏ không thể làm tròn chia đều
      const splitAmount = Math.round(totalAmount / count / 1000) * 1000;
      if (splitAmount === 0 && totalAmount > 0) {
        throw new BadRequestException(
          'Số tiền quá nhỏ để thực hiện chia đều theo đơn vị nghìn đồng.',
        );
      }

      // SỬA: Xóa bằng manager.createQueryBuilder để đưa vào transaction rollback khi lỗi
      await manager
        .createQueryBuilder()
        .delete()
        .from(ExpenseParticipant)
        .where('expenseId = :expenseId', { expenseId: expense.id })
        .execute();

      // Cập nhật thông tin thực thể expense
      Object.assign(expense, {
        title: dto.title,
        category: dto.category,
        note: dto.note,
        amount: totalAmount,
        time: dto.time,
        status:
          req.userRole === GroupRole.OWNER
            ? ExpenseStatus.APPROVED
            : ExpenseStatus.PENDING,
        trip: { id: trip.id },
        paidBy: { id: dto.paidBy },
      });

      // SỬA: Lưu bằng manager.save
      const updatedExpense = await manager.save(Expense, expense);

      // Tính toán lại tiền và bù trừ phần dư cho danh sách mới
      const totalSplit = splitAmount * count;
      const remainder = totalAmount - totalSplit;

      const participants = dto.participants!.map((userId, index) => {
        const finalAmount = index === 0 ? splitAmount + remainder : splitAmount;

        return participantRepo.create({
          expense: { id: updatedExpense.id },
          user: { id: userId },
          amount: finalAmount,
        });
      });

      // SỬA: Lưu bằng manager.save
      await manager.save(ExpenseParticipant, participants);

      if (req.userRole !== GroupRole.OWNER) {
        await this.addNotification(trip, updatedExpense);
      }

      return { message: 'Sửa thành công' };
    });
  }

  async remove(id: string, userId: string) {
    return this.dataSource.transaction(async (manager) => {
      const expenseRepo = manager.getRepository(Expense);
      const participantRepo = manager.getRepository(ExpenseParticipant);

      const expense = await expenseRepo.findOne({
        where: { id },
        relations: ['createdBy', 'paidBy', 'participants'],
      });

      if (!expense) {
        throw new NotFoundException('Chi phí không tồn tại');
      }

      if (expense.createdBy.id !== userId && expense.paidBy.id !== userId) {
        throw new ForbiddenException('Không có quyền xoá');
      }

      await participantRepo.remove(expense.participants);
      await expenseRepo.remove(expense);

      return { message: 'Xóa thành công' };
    });
  }

  async approve(trip: Trip, id: string, userId: string) {
    return this.dataSource.transaction(async (manager) => {
      const expenseRepo = manager.getRepository(Expense);

      const expense = await expenseRepo.findOne({
        where: { id },
        relations: ['trip', 'trip.group', 'trip.group.members', 'paidBy', 'createdBy'],
      });

      if (!expense) {
        throw new NotFoundException('Chi phí không tồn tại');
      }

      if (expense.status !== ExpenseStatus.PENDING) {
        throw new BadRequestException('Chi phí đã được duyệt hoặc từ chối');
      }

      expense.status = ExpenseStatus.APPROVED;
      await expenseRepo.save(expense);

      if (trip.group) {
        const payload = NotificationHelper.approveExpense({
          trip,
          expense,
          group: trip.group,
          user: expense.createdBy,
          createdBy: expense.createdBy.id,
        });

        await this.notificationService.createAndPush(payload);
      }

      return { message: 'Duyệt chi phí thành công' };
    });
  }

  async reject(trip: any, id: string, userId: string, reason?: string) {
    return this.dataSource.transaction(async (manager) => {
      const expenseRepo = manager.getRepository(Expense);

      const expense = await expenseRepo.findOne({
        where: { id },
        relations: ['trip', 'trip.group', 'trip.group.members', 'paidBy', 'createdBy'],
      });

      if (!expense) {
        throw new NotFoundException('Chi phí không tồn tại');
      }

      if (expense.status !== ExpenseStatus.PENDING) {
        throw new BadRequestException('Chi phí đã được duyệt hoặc từ chối');
      }

      expense.status = ExpenseStatus.REJECTED;
      expense.rejectionReason = reason || 'Không có lý do';
      await expenseRepo.save(expense);
    
      if (trip.group) {
        const payload = NotificationHelper.rejectExpense({
          trip,
          expense,
          group: trip.group,
          user: expense.createdBy,
          createdBy: expense.createdBy.id,
        });

        await this.notificationService.createAndPush(payload);
      }

      return { message: 'Từ chối chi phí thành công' };
    });
  }

  async addNotification(trip: Trip, expense: Expense) {
    if (trip.group) {
      const payload = NotificationHelper.newExpense({
        trip,
        expense,
        group: trip.group,
        user: trip.group.members.find((m) => m.role === GroupRole.OWNER)
          ?.user as User,
        createdBy: expense.createdBy.id,
      });

      await this.notificationService.createAndPush(payload);
    }
  }
}
