import { Injectable, BadRequestException, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { InviteCode, InviteCodeUse } from './invite-codes.entity';
import { CreateInviteCodeDto, UpdateInviteCodeDto } from './invite-codes.dto';

@Injectable()
export class InviteCodesService {
  constructor(
    @InjectRepository(InviteCode)
    private inviteCodeRepository: Repository<InviteCode>,
    @InjectRepository(InviteCodeUse)
    private inviteCodeUseRepository: Repository<InviteCodeUse>,
  ) {}

  async create(dto: CreateInviteCodeDto, createdBy?: string): Promise<InviteCode> {
    // Validate code format
    if (!/^[A-Z0-9\-]+$/.test(dto.code)) {
      throw new BadRequestException('Code must contain only uppercase letters, numbers, and hyphens');
    }

    // Check if code already exists
    const existing = await this.inviteCodeRepository.findOne({ where: { code: dto.code } });
    if (existing) {
      throw new BadRequestException('Invite code already exists');
    }

    const inviteCode = this.inviteCodeRepository.create({
      ...dto,
      createdBy,
    });

    return this.inviteCodeRepository.save(inviteCode);
  }

  async findAll(params?: {
    page?: number;
    limit?: number;
    status?: string
  }): Promise<{ codes: InviteCode[]; total: number }> {
    const query = this.inviteCodeRepository.createQueryBuilder('invite_code');

    if (params?.status === 'active') {
      query.where('invite_code.is_active = :isActive', { isActive: true })
        .andWhere('(invite_code.expires_at IS NULL OR invite_code.expires_at > NOW())')
        .andWhere('(invite_code.max_uses IS NULL OR invite_code.uses < invite_code.max_uses)');
    } else if (params?.status === 'inactive') {
      query.where('invite_code.is_active = :isActive', { isActive: false });
    } else if (params?.status === 'expired') {
      query.where('invite_code.expires_at <= NOW()');
    } else if (params?.status === 'exhausted') {
      query.where('invite_code.max_uses IS NOT NULL AND invite_code.uses >= invite_code.max_uses');
    }

    const page = params?.page || 1;
    const limit = params?.limit || 10;
    const skip = (page - 1) * limit;

    query.orderBy('invite_code.created_at', 'DESC')
      .skip(skip)
      .take(limit);

    const [codes, total] = await query.getManyAndCount();

    return { codes, total };
  }

  async findOne(id: string): Promise<InviteCode> {
    const inviteCode = await this.inviteCodeRepository.findOne({
      where: { id },
      relations: ['usages'],
    });

    if (!inviteCode) {
      throw new NotFoundException('Invite code not found');
    }

    return inviteCode;
  }

  async findByCode(code: string): Promise<InviteCode> {
    const inviteCode = await this.inviteCodeRepository.findOne({
      where: { code },
    });

    if (!inviteCode) {
      throw new NotFoundException('Invite code not found');
    }

    return inviteCode;
  }

  async update(id: string, dto: UpdateInviteCodeDto): Promise<InviteCode> {
    const inviteCode = await this.findOne(id);

    if (dto.code && dto.code !== inviteCode.code) {
      // Check if new code already exists
      const existing = await this.inviteCodeRepository.findOne({ where: { code: dto.code } });
      if (existing) {
        throw new BadRequestException('Invite code already exists');
      }
    }

    Object.assign(inviteCode, dto);
    return this.inviteCodeRepository.save(inviteCode);
  }

  async delete(id: string): Promise<void> {
    const inviteCode = await this.findOne(id);
    await this.inviteCodeRepository.remove(inviteCode);
  }

  async validateCode(code: string): Promise<{
    isValid: boolean;
    reason?: string;
    inviteCode?: InviteCode;
  }> {
    const inviteCode = await this.inviteCodeRepository.findOne({
      where: { code, isActive: true },
    });

    if (!inviteCode) {
      return { isValid: false, reason: 'Invalid invite code' };
    }

    // Check if expired
    if (inviteCode.expiresAt && inviteCode.expiresAt < new Date()) {
      return { isValid: false, reason: 'Invite code has expired' };
    }

    // Check if max uses reached
    if (inviteCode.maxUses && inviteCode.uses >= inviteCode.maxUses) {
      return { isValid: false, reason: 'Invite code has reached maximum uses' };
    }

    return { isValid: true, inviteCode };
  }

  async useCode(code: string, userId: string, userEmail: string, ipAddress?: string, userAgent?: string): Promise<void> {
    const validation = await this.validateCode(code);

    if (!validation.isValid || !validation.inviteCode) {
      throw new BadRequestException(validation.reason || 'Invalid invite code');
    }

    // Check if user already used this code
    const existingUse = await this.inviteCodeUseRepository.findOne({
      where: {
        inviteCodeId: validation.inviteCode.id,
        usedBy: userId,
      },
    });

    if (existingUse) {
      throw new BadRequestException('You have already used this invite code');
    }

    // Record the use
    const use = this.inviteCodeUseRepository.create({
      inviteCodeId: validation.inviteCode.id,
      usedBy: userId,
      userEmail,
      userIp: ipAddress,
      userAgent,
    });

    await this.inviteCodeUseRepository.save(use);
  }

  async getUsageStats(inviteCodeId: string): Promise<{
    totalUses: number;
    recentUses: InviteCodeUse[];
  }> {
    const [recentUses, totalUses] = await Promise.all([
      this.inviteCodeUseRepository.find({
        where: { inviteCodeId },
        order: { usedAt: 'DESC' },
        take: 10,
        relations: ['user'],
      }),
      this.inviteCodeUseRepository.count({ where: { inviteCodeId } }),
    ]);

    return { totalUses, recentUses };
  }

  async generateRandomCode(): Promise<string> {
    const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    let code = '';

    for (let i = 0; i < 8; i++) {
      code += chars.charAt(Math.floor(Math.random() * chars.length));
      if (i === 3) code += '-';
    }

    // Check if code exists and regenerate if needed
    const existing = await this.inviteCodeRepository.findOne({ where: { code } });
    if (existing) {
      return this.generateRandomCode();
    }

    return code;
  }
}