import {
  Injectable,
  UnauthorizedException,
  ForbiddenException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import { UserEntity } from '../entities/user.entity';
import { TenantEntity } from '../entities/tenant.entity';
import { TenantSettingsEntity } from '../entities/tenant-settings.entity';
import { RolePermissionEntity } from '../entities/role-permission.entity';
import { RedisService } from '../redis/redis.service';
import { LoginDto, AuthResponseDto } from './dto';
import { serviceMessages } from '../common/constants/messages';

const MSG = serviceMessages('User');

export interface JwtPayload {
  sub: string;
  email: string;
  tenant_id: string;
}

/**
 * Lockout config:
 * - MAX_ATTEMPTS: 5 failed login attempts
 * - LOCKOUT_DURATION: 15 minutes (900 seconds)
 * - Redis key: lockout:{tenantId}:{email}
 */
const MAX_ATTEMPTS = 5;
const LOCKOUT_DURATION = 60; // 1 minute in seconds
const LOCKOUT_PREFIX = 'lockout:';

@Injectable()
export class AuthService {
  constructor(
    @InjectRepository(UserEntity)
    private readonly userRepo: Repository<UserEntity>,
    @InjectRepository(TenantEntity)
    private readonly tenantRepo: Repository<TenantEntity>,
    @InjectRepository(TenantSettingsEntity)
    private readonly settingsRepo: Repository<TenantSettingsEntity>,
    @InjectRepository(RolePermissionEntity)
    private readonly rolePermRepo: Repository<RolePermissionEntity>,
    private readonly jwtService: JwtService,
    private readonly redisService: RedisService,
  ) {}

  /**
   * Build permissions map for a role: { module: access_level }
   */
  private async getPermissionsMap(
    roleId: string,
    tenantId: string,
  ): Promise<Record<string, string>> {
    const perms = await this.rolePermRepo.find({
      where: {
        role: { id: roleId },
        tenant_id: tenantId,
        is_deleted: false,
      },
      select: ['module', 'access_level'],
    });

    const map: Record<string, string> = {};
    for (const p of perms) {
      map[p.module] = p.access_level;
    }
    return map;
  }

  /**
   * Get lockout Redis key for a tenant+email combination.
   */
  private getLockoutKey(tenantId: string, email: string): string {
    return `${LOCKOUT_PREFIX}${tenantId}:${email.toLowerCase()}`;
  }

  /**
   * Check if account is locked and throw if so.
   */
  private async checkLockout(tenantId: string, email: string): Promise<void> {
    const key = this.getLockoutKey(tenantId, email);
    const data = await this.redisService.get(key);

    if (data) {
      const { attempts, lockedUntil } = JSON.parse(data);

      if (lockedUntil) {
        const remainingMs = new Date(lockedUntil).getTime() - Date.now();
        if (remainingMs > 0) {
          const remainingMin = Math.ceil(remainingMs / 60000);
          throw new ForbiddenException(
            `Account locked due to too many failed attempts. Try again in ${remainingMin} minute${remainingMin > 1 ? 's' : ''}.`,
          );
        }
        // Lock expired — clear it
        await this.redisService.del(key);
      }
    }
  }

  /**
   * Record a failed login attempt. Lock account after MAX_ATTEMPTS.
   */
  private async recordFailedAttempt(tenantId: string, email: string): Promise<void> {
    const key = this.getLockoutKey(tenantId, email);
    const data = await this.redisService.get(key);

    let attempts = 1;
    if (data) {
      const parsed = JSON.parse(data);
      attempts = (parsed.attempts || 0) + 1;
    }

    const payload: any = { attempts };

    if (attempts >= MAX_ATTEMPTS) {
      // Lock the account
      payload.lockedUntil = new Date(Date.now() + LOCKOUT_DURATION * 1000).toISOString();
    }

    await this.redisService.set(key, JSON.stringify(payload), LOCKOUT_DURATION);
  }

  /**
   * Clear failed attempts on successful login.
   */
  private async clearFailedAttempts(tenantId: string, email: string): Promise<void> {
    await this.redisService.del(this.getLockoutKey(tenantId, email));
  }

  async login(dto: LoginDto, tenantId: string): Promise<AuthResponseDto> {
    // Check lockout BEFORE any DB query
    await this.checkLockout(tenantId, dto.email);

    const user = await this.userRepo.findOne({
      where: {
        email: dto.email,
        tenant_id: tenantId,
        is_deleted: false,
      },
      relations: ['role'],
    });

    if (!user) {
      await this.recordFailedAttempt(tenantId, dto.email);
      throw new UnauthorizedException('Invalid email or password');
    }

    if (!user.is_active) {
      throw new UnauthorizedException('Account is deactivated');
    }

    const isPasswordValid = await bcrypt.compare(dto.password, user.password_hash);
    if (!isPasswordValid) {
      await this.recordFailedAttempt(tenantId, dto.email);
      throw new UnauthorizedException('Invalid email or password');
    }

    // Success — clear any failed attempts
    await this.clearFailedAttempts(tenantId, dto.email);

    await this.userRepo.update(user.id, { last_login_at: new Date() });

    const payload: JwtPayload = {
      sub: user.id,
      email: user.email,
      tenant_id: user.tenant_id,
    };

    const accessToken = this.jwtService.sign(payload);

    // Fetch tenant name + settings + permissions
    const tenant = await this.tenantRepo.findOne({
      where: { id: user.tenant_id },
      select: ['name'],
    });

    const tenantSettings = await this.settingsRepo.findOne({
      where: { tenant_id: user.tenant_id, is_deleted: false },
      select: ['primary_color', 'secondary_color', 'logo_url'],
    });

    const permissions = user.role_id
      ? await this.getPermissionsMap(user.role_id, user.tenant_id)
      : {};

    return {
      access_token: accessToken,
      user: {
        id: user.id,
        name: user.name,
        email: user.email,
        tenant_id: user.tenant_id,
        tenant_name: tenant?.name || null,
        role: user.role?.name || null,
        role_id: user.role_id || null,
        permissions,
      },
      theme: {
        primary_color: tenantSettings?.primary_color || '#10b981',
        secondary_color: tenantSettings?.secondary_color || '#1e293b',
        logo_url: tenantSettings?.logo_url || null,
      },
    };
  }

  async validateUserFromToken(payload: JwtPayload): Promise<UserEntity | null> {
    return this.userRepo.findOne({
      where: {
        id: payload.sub,
        tenant_id: payload.tenant_id,
        is_deleted: false,
        is_active: true,
      },
      relations: ['role'],
    });
  }

  async getProfile(userId: string, tenantId: string) {
    const user = await this.userRepo.findOne({
      where: {
        id: userId,
        tenant_id: tenantId,
        is_deleted: false,
      },
      relations: ['role'],
    });

    if (!user) {
      throw new UnauthorizedException(MSG.NOT_FOUND);
    }

    const permissions = user.role_id
      ? await this.getPermissionsMap(user.role_id, user.tenant_id)
      : {};

    return {
      id: user.id,
      name: user.name,
      email: user.email,
      phone: user.phone,
      tenant_id: user.tenant_id,
      role: user.role?.name || null,
      role_id: user.role_id || null,
      permissions,
    };
  }
}
