import { Repository, IsNull, In } from 'typeorm';
import { UserProfile, UserProfileAssignment, ProfileModule, ProfileTool, User } from '../entities';

export interface ServiceContext {
  tenantId: string;
  userId?: string;
}

export interface CreateUserProfileDto {
  code: string;
  name: string;
  description?: string;
  moduleIds?: string[];
  toolIds?: string[];
  isDefault?: boolean;
  isActive?: boolean;
}

export interface UpdateUserProfileDto {
  code?: string;
  name?: string;
  description?: string;
  moduleIds?: string[];
  toolIds?: string[];
  isDefault?: boolean;
  isActive?: boolean;
}

export interface UserProfileFilters {
  code?: string;
  isDefault?: boolean;
  isActive?: boolean;
  search?: string;
}

export interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

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

  async create(ctx: ServiceContext, data: CreateUserProfileDto): Promise<UserProfile> {
    const existing = await this.profileRepository.findOne({
      where: { tenantId: ctx.tenantId, code: data.code, deletedAt: IsNull() },
    });

    if (existing) {
      throw new Error(`Profile with code '${data.code}' already exists`);
    }

    // If this is default, remove default from others
    if (data.isDefault) {
      await this.clearDefaultProfile(ctx);
    }

    const profile = this.profileRepository.create({
      tenantId: ctx.tenantId,
      code: data.code,
      name: data.name,
      description: data.description,
      isDefault: data.isDefault ?? false,
      isActive: data.isActive ?? true,
      createdBy: ctx.userId ?? null,
    });

    const savedProfile = await this.profileRepository.save(profile);

    if (data.moduleIds?.length) {
      await this.syncModules(ctx, savedProfile.id, data.moduleIds);
    }

    if (data.toolIds?.length) {
      await this.syncTools(ctx, savedProfile.id, data.toolIds);
    }

    return this.findById(ctx, savedProfile.id) as Promise<UserProfile>;
  }

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

  async findByCode(ctx: ServiceContext, code: string): Promise<UserProfile | null> {
    return this.profileRepository.findOne({
      where: { tenantId: ctx.tenantId, code, deletedAt: IsNull() },
      relations: ['modules', 'tools'],
    });
  }

  async findAll(ctx: ServiceContext): Promise<UserProfile[]> {
    return this.profileRepository.find({
      where: { tenantId: ctx.tenantId, deletedAt: IsNull() },
      relations: ['modules', 'tools'],
      order: { name: 'ASC' },
    });
  }

  async findDefault(ctx: ServiceContext): Promise<UserProfile | null> {
    return this.profileRepository.findOne({
      where: { tenantId: ctx.tenantId, isDefault: true, deletedAt: IsNull() },
      relations: ['modules', 'tools'],
    });
  }

  async findWithFilters(
    ctx: ServiceContext,
    filters: UserProfileFilters,
    page = 1,
    limit = 50,
  ): Promise<PaginatedResult<UserProfile>> {
    const qb = this.profileRepository
      .createQueryBuilder('profile')
      .leftJoinAndSelect('profile.modules', 'modules')
      .leftJoinAndSelect('profile.tools', 'tools')
      .where('profile.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('profile.deleted_at IS NULL');

    if (filters.code) {
      qb.andWhere('profile.code = :code', { code: filters.code });
    }

    if (filters.isDefault !== undefined) {
      qb.andWhere('profile.is_default = :isDefault', { isDefault: filters.isDefault });
    }

    if (filters.isActive !== undefined) {
      qb.andWhere('profile.is_active = :isActive', { isActive: filters.isActive });
    }

    if (filters.search) {
      qb.andWhere('(profile.name ILIKE :search OR profile.code ILIKE :search)', {
        search: `%${filters.search}%`,
      });
    }

    const [data, total] = await qb
      .orderBy('profile.name', 'ASC')
      .skip((page - 1) * limit)
      .take(limit)
      .getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  async update(ctx: ServiceContext, id: string, data: UpdateUserProfileDto): Promise<UserProfile | null> {
    const profile = await this.findById(ctx, id);
    if (!profile) return null;

    if (data.code && data.code !== profile.code) {
      const existing = await this.profileRepository.findOne({
        where: { tenantId: ctx.tenantId, code: data.code, deletedAt: IsNull() },
      });
      if (existing) {
        throw new Error(`Profile with code '${data.code}' already exists`);
      }
    }

    if (data.isDefault && !profile.isDefault) {
      await this.clearDefaultProfile(ctx);
    }

    Object.assign(profile, {
      code: data.code ?? profile.code,
      name: data.name ?? profile.name,
      description: data.description ?? profile.description,
      isDefault: data.isDefault ?? profile.isDefault,
      isActive: data.isActive ?? profile.isActive,
      updatedBy: ctx.userId ?? null,
    });

    await this.profileRepository.save(profile);

    if (data.moduleIds !== undefined) {
      await this.syncModules(ctx, id, data.moduleIds);
    }

    if (data.toolIds !== undefined) {
      await this.syncTools(ctx, id, data.toolIds);
    }

    return this.findById(ctx, id);
  }

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

    profile.deletedAt = new Date();
    profile.deletedBy = ctx.userId ?? null;
    await this.profileRepository.save(profile);

    return true;
  }

  // Module Management
  async syncModules(ctx: ServiceContext, profileId: string, moduleIds: string[]): Promise<void> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) throw new Error('Profile not found');

    const modules = await this.moduleRepository.find({
      where: { id: In(moduleIds), tenantId: ctx.tenantId },
    });

    profile.modules = modules;
    await this.profileRepository.save(profile);
  }

  async addModule(ctx: ServiceContext, profileId: string, moduleId: string): Promise<void> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) throw new Error('Profile not found');

    const module = await this.moduleRepository.findOne({
      where: { id: moduleId, tenantId: ctx.tenantId },
    });
    if (!module) throw new Error('Module not found');

    profile.modules = [...(profile.modules || []), module];
    await this.profileRepository.save(profile);
  }

  async removeModule(ctx: ServiceContext, profileId: string, moduleId: string): Promise<void> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) throw new Error('Profile not found');

    profile.modules = (profile.modules || []).filter(m => m.id !== moduleId);
    await this.profileRepository.save(profile);
  }

  // Tool Management
  async syncTools(ctx: ServiceContext, profileId: string, toolIds: string[]): Promise<void> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) throw new Error('Profile not found');

    const tools = await this.toolRepository.find({
      where: { id: In(toolIds), tenantId: ctx.tenantId },
    });

    profile.tools = tools;
    await this.profileRepository.save(profile);
  }

  async addTool(ctx: ServiceContext, profileId: string, toolId: string): Promise<void> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) throw new Error('Profile not found');

    const tool = await this.toolRepository.findOne({
      where: { id: toolId, tenantId: ctx.tenantId },
    });
    if (!tool) throw new Error('Tool not found');

    profile.tools = [...(profile.tools || []), tool];
    await this.profileRepository.save(profile);
  }

  async removeTool(ctx: ServiceContext, profileId: string, toolId: string): Promise<void> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) throw new Error('Profile not found');

    profile.tools = (profile.tools || []).filter(t => t.id !== toolId);
    await this.profileRepository.save(profile);
  }

  // User Assignment
  async assignToUser(ctx: ServiceContext, userId: string, profileId: string): Promise<UserProfileAssignment> {
    const existing = await this.assignmentRepository.findOne({
      where: { tenantId: ctx.tenantId, userId, profileId },
    });

    if (existing) return existing;

    const assignment = this.assignmentRepository.create({
      tenantId: ctx.tenantId,
      userId,
      profileId,
      createdBy: ctx.userId ?? null,
    });

    return this.assignmentRepository.save(assignment);
  }

  async removeFromUser(ctx: ServiceContext, userId: string, profileId: string): Promise<boolean> {
    const result = await this.assignmentRepository.delete({
      tenantId: ctx.tenantId,
      userId,
      profileId,
    });

    return (result.affected ?? 0) > 0;
  }

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

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

  async getProfileUsers(ctx: ServiceContext, profileId: string): Promise<User[]> {
    const assignments = await this.assignmentRepository.find({
      where: { tenantId: ctx.tenantId, profileId },
      relations: ['user'],
    });

    return assignments.map(a => a.user).filter(u => u && !u.deletedAt);
  }

  private async clearDefaultProfile(ctx: ServiceContext): Promise<void> {
    await this.profileRepository.update(
      { tenantId: ctx.tenantId, isDefault: true },
      { isDefault: false },
    );
  }
}
