import { Repository, FindOptionsWhere, In } from 'typeorm';
import { UserProfile, ProfileTool, ProfileModule, UserProfileAssignment } from '../entities';
import { CreateProfileDto, UpdateProfileDto, AssignProfileDto, CreateProfileToolDto } from '../dto';

export class ProfilesService {
  constructor(
    private readonly profileRepository: Repository<UserProfile>,
    private readonly toolRepository: Repository<ProfileTool>,
    private readonly moduleRepository: Repository<ProfileModule>,
    private readonly assignmentRepository: Repository<UserProfileAssignment>
  ) {}

  // ============================================
  // PROFILE CRUD
  // ============================================

  async findAll(tenantId?: string): Promise<UserProfile[]> {
    const where: FindOptionsWhere<UserProfile> = {};

    if (tenantId) {
      // Include system profiles (tenantId = null) and tenant-specific profiles
      return this.profileRepository.find({
        where: [{ tenantId }, { tenantId: undefined, isSystem: true }],
        relations: ['tools', 'modules'],
        order: { code: 'ASC' },
      });
    }

    return this.profileRepository.find({
      relations: ['tools', 'modules'],
      order: { code: 'ASC' },
    });
  }

  async findOne(id: string): Promise<UserProfile | null> {
    return this.profileRepository.findOne({
      where: { id },
      relations: ['tools', 'modules'],
    });
  }

  async findByCode(code: string, tenantId?: string): Promise<UserProfile | null> {
    if (tenantId) {
      // First try tenant-specific, then system profile
      const tenantProfile = await this.profileRepository.findOne({
        where: { code, tenantId },
        relations: ['tools', 'modules'],
      });

      if (tenantProfile) return tenantProfile;

      return this.profileRepository.findOne({
        where: { code, isSystem: true },
        relations: ['tools', 'modules'],
      });
    }

    return this.profileRepository.findOne({
      where: { code },
      relations: ['tools', 'modules'],
    });
  }

  async create(tenantId: string, dto: CreateProfileDto, createdBy?: string): Promise<UserProfile> {
    const profile = this.profileRepository.create({
      ...dto,
      tenantId,
      createdBy,
    });

    return this.profileRepository.save(profile);
  }

  async update(id: string, dto: UpdateProfileDto, updatedBy?: string): Promise<UserProfile | null> {
    const profile = await this.findOne(id);
    if (!profile) return null;

    // Don't allow updating system profiles
    if (profile.isSystem) {
      throw new Error('Cannot update system profiles');
    }

    Object.assign(profile, dto, { updatedBy });
    return this.profileRepository.save(profile);
  }

  async delete(id: string): Promise<boolean> {
    const profile = await this.findOne(id);
    if (!profile) return false;

    // Don't allow deleting system profiles
    if (profile.isSystem) {
      throw new Error('Cannot delete system profiles');
    }

    await this.profileRepository.softDelete(id);
    return true;
  }

  // ============================================
  // PROFILE TOOLS
  // ============================================

  async addTool(profileId: string, dto: CreateProfileToolDto): Promise<ProfileTool> {
    const tool = this.toolRepository.create({
      ...dto,
      profileId,
    });

    return this.toolRepository.save(tool);
  }

  async removeTool(profileId: string, toolCode: string): Promise<boolean> {
    const result = await this.toolRepository.delete({ profileId, toolCode });
    return (result.affected ?? 0) > 0;
  }

  async getToolsForProfile(profileId: string): Promise<ProfileTool[]> {
    return this.toolRepository.find({
      where: { profileId, isActive: true },
      order: { sortOrder: 'ASC' },
    });
  }

  async getToolsForUser(userId: string, platform?: string): Promise<ProfileTool[]> {
    const assignments = await this.assignmentRepository.find({
      where: { userId },
      relations: ['profile', 'profile.tools'],
    });

    const allTools: ProfileTool[] = [];

    for (const assignment of assignments) {
      if (assignment.profile?.tools) {
        for (const tool of assignment.profile.tools) {
          if (!tool.isActive) continue;

          // Filter by platform
          if (platform === 'mobile' && tool.isWebOnly) continue;
          if (platform === 'web' && tool.isMobileOnly) continue;

          allTools.push(tool);
        }
      }
    }

    // Remove duplicates and sort
    const uniqueTools = allTools.filter(
      (tool, index, self) => index === self.findIndex((t) => t.toolCode === tool.toolCode)
    );

    return uniqueTools.sort((a, b) => a.sortOrder - b.sortOrder);
  }

  // ============================================
  // USER PROFILE ASSIGNMENTS
  // ============================================

  async assignProfile(dto: AssignProfileDto, assignedBy?: string): Promise<UserProfileAssignment> {
    // If setting as primary, unset other primary assignments for this user
    if (dto.isPrimary) {
      await this.assignmentRepository.update({ userId: dto.userId, isPrimary: true }, { isPrimary: false });
    }

    const existing = await this.assignmentRepository.findOne({
      where: { userId: dto.userId, profileId: dto.profileId },
    });

    if (existing) {
      Object.assign(existing, {
        isPrimary: dto.isPrimary ?? existing.isPrimary,
        expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : existing.expiresAt,
      });
      return this.assignmentRepository.save(existing);
    }

    const assignment = this.assignmentRepository.create({
      userId: dto.userId,
      profileId: dto.profileId,
      isPrimary: dto.isPrimary ?? false,
      expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : undefined,
      assignedBy,
    });

    return this.assignmentRepository.save(assignment);
  }

  async unassignProfile(userId: string, profileId: string): Promise<boolean> {
    const result = await this.assignmentRepository.delete({ userId, profileId });
    return (result.affected ?? 0) > 0;
  }

  async getUserProfiles(userId: string): Promise<UserProfile[]> {
    const assignments = await this.assignmentRepository.find({
      where: { userId },
      relations: ['profile', 'profile.tools', 'profile.modules'],
    });

    return assignments.map((a) => a.profile).filter((p) => p != null);
  }

  async getPrimaryProfile(userId: string): Promise<UserProfile | null> {
    const assignment = await this.assignmentRepository.findOne({
      where: { userId, isPrimary: true },
      relations: ['profile', 'profile.tools', 'profile.modules'],
    });

    return assignment?.profile ?? null;
  }

  async getUsersWithProfile(profileId: string): Promise<string[]> {
    const assignments = await this.assignmentRepository.find({
      where: { profileId },
      select: ['userId'],
    });

    return assignments.map((a) => a.userId);
  }

  // ============================================
  // PERMISSIONS CHECK
  // ============================================

  async hasModuleAccess(userId: string, moduleCode: string): Promise<boolean> {
    const profiles = await this.getUserProfiles(userId);

    for (const profile of profiles) {
      // Admin has access to all
      if (profile.availableModules.includes('all')) return true;

      // Check specific module
      if (profile.availableModules.includes(moduleCode)) return true;

      // Check profile modules with access level
      const moduleAccess = profile.modules?.find((m) => m.moduleCode === moduleCode);
      if (moduleAccess) return true;
    }

    return false;
  }

  async getModuleAccessLevel(userId: string, moduleCode: string): Promise<string | null> {
    const profiles = await this.getUserProfiles(userId);

    let highestAccess: string | null = null;
    const accessLevels = { read: 1, write: 2, admin: 3 };

    for (const profile of profiles) {
      const moduleAccess = profile.modules?.find((m) => m.moduleCode === moduleCode);
      if (moduleAccess) {
        const currentLevel = accessLevels[moduleAccess.accessLevel as keyof typeof accessLevels] ?? 0;
        const highestLevel = highestAccess ? accessLevels[highestAccess as keyof typeof accessLevels] ?? 0 : 0;

        if (currentLevel > highestLevel) {
          highestAccess = moduleAccess.accessLevel;
        }
      }
    }

    return highestAccess;
  }

  async hasPlatformAccess(userId: string, platform: string): Promise<boolean> {
    const profiles = await this.getUserProfiles(userId);

    for (const profile of profiles) {
      if (profile.includedPlatforms.includes(platform)) return true;
    }

    return false;
  }
}
