import { Injectable, UnauthorizedException, ConflictException, BadRequestException, NotFoundException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../../common/prisma.service';
import { RegisterDto, LoginDto, RequestOtpDto, VerifyOtpDto, RequestPasswordResetDto, ResetPasswordDto } from './auth.dto';
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';

@Injectable()
export class AuthService {
  constructor(private prisma: PrismaService, private jwt: JwtService) {}

  async register(dto: RegisterDto) {
    const exists = await this.prisma.user.findFirst({
      where: { OR: [{ email: dto.email }, { phone: dto.phone }] },
    });
    if (exists) throw new ConflictException('Email or phone already registered');
    const passwordHash = await bcrypt.hash(dto.password, 10);
    const user = await this.prisma.user.create({
      data: { 
        fullName: dto.fullName, 
        email: dto.email, 
        phone: dto.phone, 
        passwordHash,
        nationality: dto.nationality,
        nationalId: dto.nationalId,
        passportNumber: dto.passportNumber
      },
    });
    const passenger = await this.prisma.passenger.create({ data: { userId: user.id } });
    await this.prisma.loyaltyAccount.create({ data: { passengerId: passenger.id } });
    await this.prisma.walletAccount.create({ data: { passengerId: passenger.id } });
    await this.prisma.userPreferences.create({ data: { userId: user.id } });
    await this.createAuditLog(user.id, 'USER_REGISTERED', 'User', user.id, null, { email: user.email });
    return await this.signToken(user.id, user.email, user.role, passenger.id);
  }

  async login(dto: LoginDto) {
    const user = await this.prisma.user.findUnique({
      where: { email: dto.email },
      include: { passenger: true, agent: true },
    });
    if (!user) throw new UnauthorizedException('Invalid credentials');
    
    if (user.lockedUntil && user.lockedUntil > new Date()) {
      throw new UnauthorizedException(`Account locked until ${user.lockedUntil.toISOString()}`);
    }
    
    if (!(await bcrypt.compare(dto.password, user.passwordHash))) {
      await this.prisma.user.update({
        where: { id: user.id },
        data: { 
          failedLoginAttempts: { increment: 1 },
          lockedUntil: user.failedLoginAttempts >= 4 ? new Date(Date.now() + 15 * 60 * 1000) : null
        }
      });
      throw new UnauthorizedException('Invalid credentials');
    }
    
    await this.prisma.user.update({
      where: { id: user.id },
      data: { failedLoginAttempts: 0, lockedUntil: null, lastLoginAt: new Date() }
    });
    
    await this.createAuditLog(user.id, 'USER_LOGIN', 'User', user.id, null, null);
    
    // Ensure passenger exists and get its ID
    let passengerId = user.passenger?.id;
    if (!passengerId) {
      // If passenger doesn't exist, create it
      const passenger = await this.prisma.passenger.create({
        data: { userId: user.id }
      });
      passengerId = passenger.id;
      // Also create loyalty and wallet accounts
      await this.prisma.loyaltyAccount.create({ data: { passengerId: passenger.id } });
      await this.prisma.walletAccount.create({ data: { passengerId: passenger.id } });
    }
    
    return await this.signToken(user.id, user.email, user.role, passengerId, user.agent?.id);
  }

  async requestOtp(dto: RequestOtpDto) {
    const code = Math.floor(100000 + Math.random() * 900000).toString();
    const expiresAt = new Date(Date.now() + 10 * 60 * 1000);
    await this.prisma.otpCode.create({
      data: { email: dto.email, code, purpose: dto.purpose, expiresAt }
    });
    console.log(`[OTP] ${dto.email} - ${code} (${dto.purpose})`);
    return { sent: true, expiresIn: 600 };
  }

  async verifyOtp(dto: VerifyOtpDto) {
    const otp = await this.prisma.otpCode.findFirst({
      where: { email: dto.email, code: dto.code, purpose: dto.purpose, verified: false, expiresAt: { gt: new Date() } },
      orderBy: { createdAt: 'desc' }
    });
    if (!otp) throw new BadRequestException('Invalid or expired OTP');
    await this.prisma.otpCode.update({ where: { id: otp.id }, data: { verified: true } });
    return { verified: true };
  }

  async requestPasswordReset(dto: RequestPasswordResetDto) {
    const user = await this.prisma.user.findUnique({ where: { email: dto.email } });
    if (!user) return { sent: true };
    const token = crypto.randomBytes(32).toString('hex');
    const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
    await this.prisma.passwordResetToken.create({
      data: { userId: user.id, token, expiresAt }
    });
    console.log(`[PASSWORD_RESET] ${dto.email} - ${token}`);
    return { sent: true };
  }

  async resetPassword(dto: ResetPasswordDto) {
    const resetToken = await this.prisma.passwordResetToken.findUnique({
      where: { token: dto.token }
    });
    if (!resetToken || resetToken.used || resetToken.expiresAt < new Date()) {
      throw new BadRequestException('Invalid or expired reset token');
    }
    const passwordHash = await bcrypt.hash(dto.newPassword, 10);
    await this.prisma.user.update({
      where: { id: resetToken.userId },
      data: { passwordHash, failedLoginAttempts: 0, lockedUntil: null }
    });
    await this.prisma.passwordResetToken.update({
      where: { id: resetToken.id },
      data: { used: true }
    });
    await this.createAuditLog(resetToken.userId, 'PASSWORD_RESET', 'User', resetToken.userId, null, null);
    return { reset: true };
  }

  async getUsers(filters: { search?: string; role?: string; status?: string; page?: number; pageSize?: number }) {
    const { search, role, status, page = 1, pageSize = 10 } = filters;
    const skip = (page - 1) * pageSize;

    const where: any = {
      role: { not: 'PASSENGER' }, // Exclude passenger accounts
    };

    if (search) {
      where.OR = [
        { email: { contains: search, mode: 'insensitive' } },
        { fullName: { contains: search, mode: 'insensitive' } },
      ];
    }

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

    // For status filtering, we check if user is active (no lock/block) or inactive
    if (status === 'ACTIVE') {
      where.AND = [
        { blockedUntil: { lte: new Date() } },
        { lockedUntil: { lte: new Date() } }
      ];
    } else if (status === 'INACTIVE') {
      where.OR = [
        { blockedUntil: { gt: new Date() } },
        { lockedUntil: { gt: new Date() } }
      ];
    }

    const [items, total] = await Promise.all([
      this.prisma.user.findMany({
        where,
        select: {
          id: true,
          email: true,
          fullName: true,
          role: true,
          lastLoginAt: true,
          createdAt: true,
          blockedUntil: true,
          lockedUntil: true,
        },
        skip,
        take: pageSize,
        orderBy: { createdAt: 'desc' },
      }),
      this.prisma.user.count({ where }),
    ]);

    return {
      items: items.map(user => ({
        id: user.id,
        email: user.email,
        fullName: user.fullName,
        role: user.role,
        lastLogin: user.lastLoginAt,
        status: (!user.blockedUntil || user.blockedUntil <= new Date()) &&
                (!user.lockedUntil || user.lockedUntil <= new Date()) 
          ? 'ACTIVE' 
          : 'INACTIVE',
      })),
      total,
      page,
      pageSize,
    };
  }

  async createUser(dto: { email: string; fullName: string; role: string; status?: string; password?: string }) {
    const exists = await this.prisma.user.findFirst({
      where: { OR: [{ email: dto.email }] },
    });
    if (exists) throw new ConflictException('Email already registered');

    const passwordHash = await bcrypt.hash(dto.password || 'TempPassword123!', 10);
    
    const user = await this.prisma.user.create({
      data: {
        email: dto.email,
        fullName: dto.fullName,
        role: dto.role as any,
        phone: dto.email, // Use email as phone temporarily for unique constraint
        passwordHash,
        blockedUntil: dto.status === 'INACTIVE' ? new Date(Date.now() + 365 * 24 * 60 * 60 * 1000) : undefined,
      },
      select: {
        id: true,
        email: true,
        fullName: true,
        role: true,
        lastLoginAt: true,
        createdAt: true,
      },
    });

    await this.createAuditLog(user.id, 'USER_CREATED', 'User', user.id, null, { email: user.email, role: dto.role });

    return user;
  }

  async updateUser(id: string, dto: Partial<{ email: string; fullName: string; role: string; status: string }>) {
    const user = await this.prisma.user.findUnique({ where: { id } });
    if (!user) throw new NotFoundException('User not found');

    const updateData: any = {};
    if (dto.fullName) updateData.fullName = dto.fullName;
    if (dto.role) updateData.role = dto.role;
    if (dto.status === 'ACTIVE') {
      updateData.blockedUntil = null;
      updateData.lockedUntil = null;
    } else if (dto.status === 'INACTIVE') {
      updateData.blockedUntil = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000);
    }

    const updated = await this.prisma.user.update({
      where: { id },
      data: updateData,
      select: {
        id: true,
        email: true,
        fullName: true,
        role: true,
        lastLoginAt: true,
        createdAt: true,
      },
    });

    await this.createAuditLog(id, 'USER_UPDATED', 'User', id, { oldData: user }, { newData: updateData });

    return updated;
  }

  async deleteUser(id: string) {
    const user = await this.prisma.user.findUnique({ where: { id } });
    if (!user) throw new NotFoundException('User not found');

    // Don't actually delete, just deactivate
    await this.prisma.user.update({
      where: { id },
      data: { blockedUntil: new Date(), lockedUntil: new Date() },
    });

    await this.createAuditLog(id, 'USER_DELETED', 'User', id, { email: user.email }, null);

    return { deleted: true };
  }

  async resetUserPassword(id: string, tempPassword: string) {
    const user = await this.prisma.user.findUnique({ where: { id } });
    if (!user) throw new NotFoundException('User not found');

    const passwordHash = await bcrypt.hash(tempPassword, 10);
    await this.prisma.user.update({
      where: { id },
      data: {
        passwordHash,
        failedLoginAttempts: 0,
        lockedUntil: null,
      },
    });

    await this.createAuditLog(id, 'PASSWORD_RESET_ADMIN', 'User', id, null, { resetBy: 'admin' });

    return { reset: true, tempPassword };
  }

  private async signToken(userId: string, email: string, role: string, passengerId?: string, agentId?: string) {
    // Get the full user data to include fullName
    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      select: { id: true, email: true, fullName: true, role: true }
    });
    
    const payload = { sub: userId, email, role, passengerId, agentId };
    console.log('[AUTH] Creating JWT with payload:', payload);
    
    const token = this.jwt.sign(payload);
    console.log('[AUTH] JWT created, token length:', token.length);
    
    const response = { 
      token, 
      user: { 
        id: userId, 
        email, 
        fullName: user?.fullName || email, 
        role, 
        passengerId, 
        agentId 
      } 
    };
    console.log('[AUTH] Returning user object with passengerId:', response.user.passengerId);
    return response;
  }

  private async createAuditLog(userId: string, action: string, entityType: string, entityId: string, oldData: any, newData: any) {
    await this.prisma.auditLog.create({
      data: { userId, action, entityType, entityId, oldData, newData }
    });
  }

  async getProfile(userId: string) {
    if (!userId) {
      throw new UnauthorizedException('User ID not found in token');
    }

    const user = await this.prisma.user.findUnique({
      where: { id: userId },
      include: {
        passenger: {
          include: {
            loyalty: true,
            wallet: true,
          },
        },
        preferences: true,
        devices: true,
      },
    });

    if (!user) throw new UnauthorizedException('User not found');

    return {
      id: user.id,
      email: user.email,
      phone: user.phone,
      fullName: user.fullName,
      role: user.role,
      nationality: user.nationality,
      nationalityCode: user.nationalityCode,
      nationalId: user.nationalId,
      passportNumber: user.passportNumber,
      faydaVerified: user.faydaVerified,
      faydaVerifiedAt: user.faydaVerifiedAt,
      lastLoginAt: user.lastLoginAt,
      createdAt: user.createdAt,
      passenger: user.passenger ? {
        id: user.passenger.id,
        preferredLanguage: user.passenger.preferredLanguage,
        loyalty: user.passenger.loyalty ? {
          tier: user.passenger.loyalty.tier,
          pointsBalance: user.passenger.loyalty.pointsBalance,
          lifetimePoints: user.passenger.loyalty.lifetimePoints,
        } : null,
        wallet: user.passenger.wallet ? {
          balanceMinor: user.passenger.wallet.balanceMinor,
          currency: user.passenger.wallet.currency,
        } : null,
      } : null,
      preferences: user.preferences,
      devices: user.devices.map(device => ({
        id: device.id,
        platform: device.platform,
        name: device.name,
        pushToken: device.pushToken,
        trusted: device.trusted,
        lastSeenAt: device.lastSeenAt,
      })),
    };
  }

  async logout(userId: string) {
    // Invalidate all active sessions for this user
    await this.prisma.session.deleteMany({
      where: { userId }
    });

    // Log the logout action
    await this.createAuditLog(userId, 'USER_LOGOUT', 'User', userId, null, null);

    return {
      success: true,
      message: 'Logged out successfully'
    };
  }
}
