import { BadRequestException, ConflictException, Injectable, NotFoundException, OnModuleInit, ForbiddenException, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { UserRole } from './user-role.enum';
import { TenantMember } from '../tenant/tenant-member.entity';
import { ApiKey } from '../auth/entities/api-key.entity';
import * as bcrypt from 'bcrypt';
import { CreateUserDto } from './dto/create-user.dto';
import * as crypto from 'crypto';
import { I18nService } from '../i18n/i18n.service';
import { TenantService } from '../tenant/tenant.service';

@Injectable()
export class UserService implements OnModuleInit {
  private readonly logger = new Logger(UserService.name);

  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
    @InjectRepository(ApiKey)
    private apiKeyRepository: Repository<ApiKey>,
    @InjectRepository(TenantMember)
    private tenantMemberRepository: Repository<TenantMember>,
    private i18nService: I18nService,
    private tenantService: TenantService,
  ) { }

  async findOneByUsername(username: string): Promise<User | null> {
    return this.usersRepository.findOne({ where: { username } });
  }

  async create(createUserDto: CreateUserDto): Promise<User> {
    const user = this.usersRepository.create(createUserDto as any);
    return this.usersRepository.save(user as any);
  }

  async onModuleInit() {
    await this.createAdminIfNotExists();
  }



  async findAll(page?: number, limit?: number): Promise<{ data: User[]; total: number }> {
    const queryBuilder = this.usersRepository.createQueryBuilder('user')
      .leftJoinAndSelect('user.tenantMembers', 'tenantMember')
      .leftJoinAndSelect('tenantMember.tenant', 'tenant')
      .select(['user.id', 'user.username', 'user.displayName', 'user.isAdmin', 'user.createdAt', 'user.tenantId', 'tenantMember', 'tenant'])
      .orderBy('user.createdAt', 'DESC');

    if (page && limit) {
      const [data, total] = await queryBuilder
        .skip((page - 1) * limit)
        .take(limit)
        .getManyAndCount();
      return { data, total };
    }

    const [data, total] = await queryBuilder.getManyAndCount();
    return { data, total };
  }

  async findByTenantId(tenantId: string, page?: number, limit?: number): Promise<{ data: User[]; total: number }> {
    const queryBuilder = this.usersRepository.createQueryBuilder('user')
      .innerJoin('user.tenantMembers', 'member', 'member.tenantId = :tenantId', { tenantId })
      .select(['user.id', 'user.username', 'user.displayName', 'user.isAdmin', 'user.createdAt', 'user.tenantId'])
      .orderBy('user.createdAt', 'DESC');

    if (page && limit) {
      const [data, total] = await queryBuilder
        .skip((page - 1) * limit)
        .take(limit)
        .getManyAndCount();
      return { data, total };
    }

    const [data, total] = await queryBuilder.getManyAndCount();
    return { data, total };
  }

  async isAdmin(userId: string): Promise<boolean> {
    const user = await this.usersRepository.findOne({
      where: { id: userId },
      select: ['isAdmin'],
    });
    return user?.isAdmin || false;
  }

  async changePassword(
    userId: string,
    currentPassword: string,
    newPassword: string,
  ): Promise<{ message: string }> {
    const user = await this.usersRepository.findOne({ where: { id: userId } });
    if (!user) {
      throw new NotFoundException(this.i18nService.getMessage('userNotFound'));
    }

    const isCurrentPasswordValid = await bcrypt.compare(
      currentPassword,
      user.password,
    );
    if (!isCurrentPasswordValid) {
      throw new BadRequestException(this.i18nService.getMessage('incorrectCurrentPassword'));
    }

    const hashedNewPassword = await bcrypt.hash(newPassword, 10);
    await this.usersRepository.update(userId, { password: hashedNewPassword });

    return { message: this.i18nService.getMessage('passwordChanged') };
  }

  async createUser(
    username: string,
    password: string,
    isAdmin: boolean = false,
    tenantId?: string,
    displayName?: string,
  ): Promise<{ message: string; user: { id: string; username: string; displayName: string; isAdmin: boolean } }> {
    const existingUser = await this.findOneByUsername(username);
    if (existingUser) {
      throw new ConflictException(this.i18nService.getMessage('usernameExists'));
    }

    const hashedPassword = await bcrypt.hash(password, 10);
    console.log(`[UserService] Creating user: ${username}, isAdmin: ${isAdmin}`);
    const user = await this.usersRepository.save({
      username,
      password: hashedPassword,
      displayName: displayName || username,
      isAdmin,
      tenantId: tenantId ?? undefined,
    } as any);

    return {
      message: this.i18nService.getMessage('userCreated'),
      user: { id: user.id, username: user.username, displayName: user.displayName, isAdmin: user.isAdmin },
    };
  }

  async findOneById(userId: string): Promise<User | null> {
    return this.usersRepository.findOne({
      where: { id: userId },
      relations: ['tenantMembers', 'tenantMembers.tenant']
    });
  }

  async findByApiKey(apiKeyValue: string): Promise<User | null> {
    const apiKey = await this.apiKeyRepository.findOne({
      where: { key: apiKeyValue },
      relations: ['user']
    });
    return apiKey ? apiKey.user : null;
  }

  async getUserTenants(userId: string): Promise<(TenantMember & { features?: { isNotebookEnabled: boolean } })[]> {
    const user = await this.usersRepository.findOne({ where: { id: userId }, select: ['isAdmin'] });

    if (user?.isAdmin) {
      const tenantsData = await this.tenantService.findAll();
      const allTenants = Array.isArray(tenantsData) ? tenantsData : tenantsData.data;
      const results = await Promise.all(allTenants.map(async t => {
        const settings = await this.tenantService.getSettings(t.id);
        return {
          tenantId: t.id,
          tenant: t,
          role: UserRole.SUPER_ADMIN,
          userId: userId,
          features: {
            isNotebookEnabled: settings?.isNotebookEnabled ?? true,
          },
        } as TenantMember & { features: { isNotebookEnabled: boolean } };
      }));
      return results;
    }

    const members = await this.tenantMemberRepository.find({
      where: { userId },
      relations: ['tenant']
    });

    // Filter out the "Default" tenant for non-super admins
    const filtered = members.filter(m => m.tenant?.name !== TenantService.DEFAULT_TENANT_NAME);

    // Attach per-tenant feature flags
    return Promise.all(filtered.map(async m => {
      const settings = await this.tenantService.getSettings(m.tenantId);
      return {
        ...m,
        features: {
          isNotebookEnabled: settings?.isNotebookEnabled ?? true,
        },
      };
    }));
  }

  /**
   * Generates a new API key for the user, or returns the existing one (first one).
   */
  async getOrCreateApiKey(userId: string): Promise<string> {
    const user = await this.usersRepository.findOne({
      where: { id: userId },
      relations: ['apiKeys']
    });

    if (!user) throw new NotFoundException(this.i18nService.getMessage('userNotFound'));

    if (user.apiKeys && user.apiKeys.length > 0) {
      return user.apiKeys[0].key;
    }

    const keyString = 'kb_' + crypto.randomBytes(32).toString('hex');
    const newApiKey = this.apiKeyRepository.create({
      userId: user.id,
      key: keyString
    });
    await this.apiKeyRepository.save(newApiKey);

    return keyString;
  }

  /**
   * Regenerates (rotates) the API key for the user.
   * This clears existing keys and creates a new one.
   */
  async regenerateApiKey(userId: string): Promise<string> {
    const user = await this.usersRepository.findOne({ where: { id: userId } });
    if (!user) throw new NotFoundException(this.i18nService.getMessage('userNotFound'));

    // Delete existing keys
    await this.apiKeyRepository.delete({ userId: user.id });

    // Create new key
    const keyString = 'kb_' + crypto.randomBytes(32).toString('hex');
    const newApiKey = this.apiKeyRepository.create({
      userId: user.id,
      key: keyString
    });
    await this.apiKeyRepository.save(newApiKey);

    return keyString;
  }

  async updateUser(
    userId: string,
    updateData: { username?: string; isAdmin?: boolean; password?: string; tenantId?: string; displayName?: string },
  ): Promise<{ message: string; user: { id: string; username: string; displayName: string; isAdmin: boolean } }> {
    const user = await this.usersRepository.findOne({ where: { id: userId } });
    if (!user) {
      throw new NotFoundException(this.i18nService.getMessage('userNotFound'));
    }

    // Hash password first if update needed
    if (updateData.password) {
      const hashedPassword = await bcrypt.hash(updateData.password, 10);
      updateData.password = hashedPassword;
    }

    // Block any changes to user "admin"
    if (user.username === 'admin') {
      throw new ForbiddenException(this.i18nService.getMessage('cannotModifyBuiltinAdmin'));
    }


    await this.usersRepository.update(userId, updateData as any);

    const updatedUser = await this.usersRepository.findOne({
      where: { id: userId },
      select: ['id', 'username', 'displayName', 'isAdmin'],
    });

    return {
      message: this.i18nService.getMessage('userInfoUpdated'),
      user: {
        id: updatedUser!.id,
        username: updatedUser!.username,
        displayName: updatedUser!.displayName,
        isAdmin: updatedUser!.isAdmin
      },
    };
  }

  async deleteUser(userId: string): Promise<{ message: string }> {
    const user = await this.usersRepository.findOne({ where: { id: userId } });
    if (!user) {
      throw new NotFoundException(this.i18nService.getMessage('userNotFound'));
    }

    // Block deletion of user "admin"
    if (user.username === 'admin') {
      throw new ForbiddenException(this.i18nService.getMessage('cannotDeleteBuiltinAdmin'));
    }

    await this.usersRepository.delete(userId);

    return {
      message: this.i18nService.getMessage('userDeleted'),
    };
  }

  async getTenantSettings(tenantId: string) {
    return this.tenantService.getSettings(tenantId);
  }

  private async createAdminIfNotExists() {
    const adminUser = await this.findOneByUsername('admin');
    if (!adminUser) {
      const randomPassword = Math.random().toString(36).slice(-8);
      const hashedPassword = await bcrypt.hash(randomPassword, 10);

      await this.usersRepository.save({
        username: 'admin',
        password: hashedPassword,
        displayName: 'Admin',
        isAdmin: true,
      });

      console.log('\n=== Admin account created ===');
      console.log('Username: admin');
      console.log('Password:', randomPassword);
      console.log('========================================\n');
    }
  }
}
