/**
 * UserProfileService - Gestión de Perfiles de Usuario
 *
 * CRUD de perfiles con asignación de herramientas y módulos.
 * Soporta multi-tenant.
 *
 * @module Profiles
 */

import { Repository, DataSource, IsNull } from 'typeorm';
import { UserProfile } from '../entities/user-profile.entity';
import { ProfileTool } from '../entities/profile-tool.entity';
import { ProfileModule } from '../entities/profile-module.entity';
import { UserProfileAssignment } from '../entities/user-profile-assignment.entity';

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

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

export interface CreateUserProfileDto {
  code: string;
  name: string;
  description?: string;
  isSystem?: boolean;
  color?: string;
  icon?: string;
  basePermissions?: string[];
  availableModules?: string[];
  monthlyPrice?: number;
  includedPlatforms?: string[];
  defaultTools?: string[];
  featureFlags?: Record<string, boolean>;
}

export interface UpdateUserProfileDto {
  name?: string;
  description?: string;
  color?: string;
  icon?: string;
  basePermissions?: string[];
  availableModules?: string[];
  monthlyPrice?: number;
  includedPlatforms?: string[];
  defaultTools?: string[];
  featureFlags?: Record<string, boolean>;
}

export interface ProfileFilters {
  search?: string;
  isSystem?: boolean;
  page?: number;
  limit?: number;
}

export interface AddToolDto {
  toolCode: string;
  toolName: string;
  description?: string;
  category?: string;
  isMobileOnly?: boolean;
  isWebOnly?: boolean;
  icon?: string;
  configuration?: Record<string, any>;
  sortOrder?: number;
}

export interface AddModuleDto {
  moduleCode: string;
  accessLevel?: 'read' | 'write' | 'admin';
  canExport?: boolean;
  canPrint?: boolean;
}

export class UserProfileService {
  private profileRepo: Repository<UserProfile>;
  private toolRepo: Repository<ProfileTool>;
  private moduleRepo: Repository<ProfileModule>;
  private assignmentRepo: Repository<UserProfileAssignment>;

  constructor(dataSource: DataSource) {
    this.profileRepo = dataSource.getRepository(UserProfile);
    this.toolRepo = dataSource.getRepository(ProfileTool);
    this.moduleRepo = dataSource.getRepository(ProfileModule);
    this.assignmentRepo = dataSource.getRepository(UserProfileAssignment);
  }

  /**
   * Create a new user profile
   */
  async create(ctx: ServiceContext, dto: CreateUserProfileDto): Promise<UserProfile> {
    // Check for duplicate code within tenant
    const existing = await this.findByCode(ctx, dto.code);
    if (existing) {
      throw new Error(`Profile with code ${dto.code} already exists`);
    }

    const profile = this.profileRepo.create({
      ...dto,
      tenantId: ctx.tenantId,
      createdBy: ctx.userId,
      updatedBy: ctx.userId,
    });

    return this.profileRepo.save(profile);
  }

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

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

  /**
   * Find all profiles with filters
   */
  async findAll(ctx: ServiceContext, filters: ProfileFilters = {}): Promise<PaginatedResult<UserProfile>> {
    const page = filters.page || 1;
    const limit = Math.min(filters.limit || 20, 100);
    const skip = (page - 1) * limit;

    const qb = this.profileRepo
      .createQueryBuilder('p')
      .leftJoinAndSelect('p.tools', 'tools')
      .leftJoinAndSelect('p.modules', 'modules')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('p.deleted_at IS NULL');

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

    if (filters.isSystem !== undefined) {
      qb.andWhere('p.is_system = :isSystem', { isSystem: filters.isSystem });
    }

    qb.orderBy('p.name', 'ASC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

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

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

    if (profile.isSystem) {
      throw new Error('Cannot modify system profiles');
    }

    Object.assign(profile, dto, { updatedBy: ctx.userId });
    return this.profileRepo.save(profile);
  }

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

    if (profile.isSystem) {
      throw new Error('Cannot delete system profiles');
    }

    // Check if profile is assigned to any users
    const assignmentCount = await this.assignmentRepo.count({
      where: { profileId: id },
    });

    if (assignmentCount > 0) {
      throw new Error(`Cannot delete profile with ${assignmentCount} active assignments`);
    }

    await this.profileRepo.update(id, { deletedAt: new Date() });
    return true;
  }

  // ============ Tool Management ============

  /**
   * Add tool to profile
   */
  async addTool(ctx: ServiceContext, profileId: string, dto: AddToolDto): Promise<ProfileTool> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) {
      throw new Error('Profile not found');
    }

    // Check if tool already exists
    const existing = await this.toolRepo.findOne({
      where: { profileId, toolCode: dto.toolCode },
    });

    if (existing) {
      throw new Error(`Tool ${dto.toolCode} already assigned to this profile`);
    }

    const tool = this.toolRepo.create({
      ...dto,
      profileId,
      isActive: true,
    });

    return this.toolRepo.save(tool);
  }

  /**
   * Update tool
   */
  async updateTool(
    ctx: ServiceContext,
    profileId: string,
    toolId: string,
    dto: Partial<AddToolDto>
  ): Promise<ProfileTool | null> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) {
      throw new Error('Profile not found');
    }

    const tool = await this.toolRepo.findOne({
      where: { id: toolId, profileId },
    });

    if (!tool) {
      return null;
    }

    Object.assign(tool, dto);
    return this.toolRepo.save(tool);
  }

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

    const result = await this.toolRepo.delete({ id: toolId, profileId });
    return (result.affected || 0) > 0;
  }

  /**
   * Get profile tools
   */
  async getTools(ctx: ServiceContext, profileId: string): Promise<ProfileTool[]> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) {
      throw new Error('Profile not found');
    }

    return this.toolRepo.find({
      where: { profileId, isActive: true },
      order: { sortOrder: 'ASC', toolName: 'ASC' },
    });
  }

  // ============ Module Management ============

  /**
   * Add module access to profile
   */
  async addModule(ctx: ServiceContext, profileId: string, dto: AddModuleDto): Promise<ProfileModule> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) {
      throw new Error('Profile not found');
    }

    // Check if module already exists
    const existing = await this.moduleRepo.findOne({
      where: { profileId, moduleCode: dto.moduleCode },
    });

    if (existing) {
      throw new Error(`Module ${dto.moduleCode} already assigned to this profile`);
    }

    const module = this.moduleRepo.create({
      ...dto,
      profileId,
      accessLevel: dto.accessLevel || 'read',
      canExport: dto.canExport ?? false,
      canPrint: dto.canPrint ?? true,
    });

    return this.moduleRepo.save(module);
  }

  /**
   * Update module access
   */
  async updateModule(
    ctx: ServiceContext,
    profileId: string,
    moduleId: string,
    dto: Partial<AddModuleDto>
  ): Promise<ProfileModule | null> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) {
      throw new Error('Profile not found');
    }

    const module = await this.moduleRepo.findOne({
      where: { id: moduleId, profileId },
    });

    if (!module) {
      return null;
    }

    Object.assign(module, dto);
    return this.moduleRepo.save(module);
  }

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

    const result = await this.moduleRepo.delete({ id: moduleId, profileId });
    return (result.affected || 0) > 0;
  }

  /**
   * Get profile modules
   */
  async getModules(ctx: ServiceContext, profileId: string): Promise<ProfileModule[]> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) {
      throw new Error('Profile not found');
    }

    return this.moduleRepo.find({
      where: { profileId },
      order: { moduleCode: 'ASC' },
    });
  }

  // ============ Profile Assignment ============

  /**
   * Assign profile to user
   */
  async assignToUser(
    ctx: ServiceContext,
    profileId: string,
    userId: string,
    options: { isPrimary?: boolean; expiresAt?: Date } = {}
  ): Promise<UserProfileAssignment> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) {
      throw new Error('Profile not found');
    }

    // Check if already assigned
    const existing = await this.assignmentRepo.findOne({
      where: { userId, profileId },
    });

    if (existing) {
      // Update existing assignment
      Object.assign(existing, {
        isPrimary: options.isPrimary ?? existing.isPrimary,
        expiresAt: options.expiresAt ?? existing.expiresAt,
      });
      return this.assignmentRepo.save(existing);
    }

    // If setting as primary, unset other primaries
    if (options.isPrimary) {
      await this.assignmentRepo.update(
        { userId, isPrimary: true },
        { isPrimary: false }
      );
    }

    const assignment = this.assignmentRepo.create({
      userId,
      profileId,
      isPrimary: options.isPrimary ?? false,
      expiresAt: options.expiresAt,
      assignedBy: ctx.userId,
    });

    return this.assignmentRepo.save(assignment);
  }

  /**
   * Remove profile from user
   */
  async removeFromUser(_ctx: ServiceContext, profileId: string, userId: string): Promise<boolean> {
    const result = await this.assignmentRepo.delete({ userId, profileId });
    return (result.affected || 0) > 0;
  }

  /**
   * Get user's profile assignments
   */
  async getUserAssignments(_ctx: ServiceContext, userId: string): Promise<UserProfileAssignment[]> {
    return this.assignmentRepo.find({
      where: { userId },
      relations: ['profile'],
      order: { isPrimary: 'DESC', assignedAt: 'DESC' },
    });
  }

  /**
   * Get users assigned to a profile
   */
  async getProfileUsers(ctx: ServiceContext, profileId: string): Promise<UserProfileAssignment[]> {
    const profile = await this.findById(ctx, profileId);
    if (!profile) {
      throw new Error('Profile not found');
    }

    return this.assignmentRepo.find({
      where: { profileId },
      order: { assignedAt: 'DESC' },
    });
  }

  // ============ Statistics ============

  /**
   * Get profile statistics
   */
  async getStats(ctx: ServiceContext): Promise<ProfileStats> {
    const profiles = await this.profileRepo.find({
      where: { tenantId: ctx.tenantId, deletedAt: IsNull() },
    });

    const assignments = await this.assignmentRepo
      .createQueryBuilder('a')
      .innerJoin('a.profile', 'p')
      .where('p.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .getCount();

    let totalMonthlyCost = 0;
    let systemCount = 0;
    let customCount = 0;

    for (const profile of profiles) {
      totalMonthlyCost += Number(profile.monthlyPrice) || 0;
      if (profile.isSystem) {
        systemCount++;
      } else {
        customCount++;
      }
    }

    return {
      totalProfiles: profiles.length,
      systemProfiles: systemCount,
      customProfiles: customCount,
      totalAssignments: assignments,
      totalMonthlyCost,
    };
  }

  /**
   * Clone a profile
   */
  async cloneProfile(
    ctx: ServiceContext,
    sourceId: string,
    newCode: string,
    newName: string
  ): Promise<UserProfile> {
    const source = await this.findById(ctx, sourceId);
    if (!source) {
      throw new Error('Source profile not found');
    }

    // Check for duplicate code
    const existing = await this.findByCode(ctx, newCode);
    if (existing) {
      throw new Error(`Profile with code ${newCode} already exists`);
    }

    // Create new profile
    const newProfile = await this.create(ctx, {
      code: newCode,
      name: newName,
      description: source.description ? `Clone of: ${source.description}` : `Clone of ${source.name}`,
      isSystem: false,
      color: source.color,
      icon: source.icon,
      basePermissions: [...source.basePermissions],
      availableModules: [...source.availableModules],
      monthlyPrice: source.monthlyPrice,
      includedPlatforms: [...source.includedPlatforms],
      defaultTools: [...source.defaultTools],
      featureFlags: { ...source.featureFlags },
    });

    // Clone tools
    for (const tool of source.tools || []) {
      await this.addTool(ctx, newProfile.id, {
        toolCode: tool.toolCode,
        toolName: tool.toolName,
        description: tool.description,
        category: tool.category,
        isMobileOnly: tool.isMobileOnly,
        isWebOnly: tool.isWebOnly,
        icon: tool.icon,
        configuration: { ...tool.configuration },
        sortOrder: tool.sortOrder,
      });
    }

    // Clone modules
    for (const module of source.modules || []) {
      await this.addModule(ctx, newProfile.id, {
        moduleCode: module.moduleCode,
        accessLevel: module.accessLevel,
        canExport: module.canExport,
        canPrint: module.canPrint,
      });
    }

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

export interface ProfileStats {
  totalProfiles: number;
  systemProfiles: number;
  customProfiles: number;
  totalAssignments: number;
  totalMonthlyCost: number;
}
