/**
 * UserBranchAssignment Service
 * Servicio para gestión de asignaciones de usuarios a sucursales
 *
 * @module Branches
 */

import { Repository, FindOptionsWhere } from 'typeorm';
import { AppDataSource } from '../../../shared/database/typeorm.config';
import {
  UserBranchAssignment,
  AssignmentType,
  BranchRole,
} from '../entities/user-branch-assignment.entity';
import { ServiceContext } from './branch.service';

export interface CreateUserBranchAssignmentDto {
  userId: string;
  branchId: string;
  assignmentType?: AssignmentType;
  branchRole?: BranchRole;
  permissions?: string[];
  validFrom?: Date;
  validUntil?: Date;
  isActive?: boolean;
}

export interface UpdateUserBranchAssignmentDto {
  assignmentType?: AssignmentType;
  branchRole?: BranchRole;
  permissions?: string[];
  validFrom?: Date;
  validUntil?: Date;
  isActive?: boolean;
}

export interface UserBranchAssignmentFilters {
  userId?: string;
  branchId?: string;
  assignmentType?: AssignmentType;
  branchRole?: BranchRole;
  isActive?: boolean;
  includeExpired?: boolean;
}

export class UserBranchAssignmentService {
  private repository: Repository<UserBranchAssignment>;

  constructor() {
    this.repository = AppDataSource.getRepository(UserBranchAssignment);
  }

  async findAll(
    ctx: ServiceContext,
    filters?: UserBranchAssignmentFilters
  ): Promise<UserBranchAssignment[]> {
    const where: FindOptionsWhere<UserBranchAssignment> = {
      tenantId: ctx.tenantId,
    };

    if (filters?.userId) {
      where.userId = filters.userId;
    }

    if (filters?.branchId) {
      where.branchId = filters.branchId;
    }

    if (filters?.assignmentType) {
      where.assignmentType = filters.assignmentType;
    }

    if (filters?.branchRole) {
      where.branchRole = filters.branchRole;
    }

    if (filters?.isActive !== undefined) {
      where.isActive = filters.isActive;
    }

    return this.repository.find({
      where,
      relations: ['branch'],
      order: { assignmentType: 'ASC', createdAt: 'DESC' },
    });
  }

  async findById(ctx: ServiceContext, id: string): Promise<UserBranchAssignment | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId },
      relations: ['branch'],
    });
  }

  async findByUser(ctx: ServiceContext, userId: string): Promise<UserBranchAssignment[]> {
    return this.repository.find({
      where: { userId, tenantId: ctx.tenantId, isActive: true },
      relations: ['branch'],
      order: { assignmentType: 'ASC' },
    });
  }

  async findByBranch(ctx: ServiceContext, branchId: string): Promise<UserBranchAssignment[]> {
    return this.repository.find({
      where: { branchId, tenantId: ctx.tenantId, isActive: true },
      relations: ['branch'],
      order: { branchRole: 'ASC' },
    });
  }

  async findPrimaryAssignment(
    ctx: ServiceContext,
    userId: string
  ): Promise<UserBranchAssignment | null> {
    return this.repository.findOne({
      where: {
        userId,
        tenantId: ctx.tenantId,
        assignmentType: 'primary',
        isActive: true,
      },
      relations: ['branch'],
    });
  }

  async findActiveAssignments(
    ctx: ServiceContext,
    userId: string,
    asOfDate?: Date
  ): Promise<UserBranchAssignment[]> {
    const now = asOfDate || new Date();

    const assignments = await this.repository.find({
      where: {
        userId,
        tenantId: ctx.tenantId,
        isActive: true,
      },
      relations: ['branch'],
    });

    // Filter by validity period
    return assignments.filter(a => {
      const validFrom = a.validFrom || new Date(0);
      const validUntil = a.validUntil || new Date('9999-12-31');
      return now >= validFrom && now <= validUntil;
    });
  }

  async isUserAssignedToBranch(
    ctx: ServiceContext,
    userId: string,
    branchId: string
  ): Promise<boolean> {
    const assignment = await this.repository.findOne({
      where: {
        userId,
        branchId,
        tenantId: ctx.tenantId,
        isActive: true,
      },
    });

    if (!assignment) {
      return false;
    }

    // Check validity
    const now = new Date();
    const validFrom = assignment.validFrom || new Date(0);
    const validUntil = assignment.validUntil || new Date('9999-12-31');

    return now >= validFrom && now <= validUntil;
  }

  async create(
    ctx: ServiceContext,
    data: CreateUserBranchAssignmentDto
  ): Promise<UserBranchAssignment> {
    // If setting as primary, unset other primary assignments for this user
    if (data.assignmentType === 'primary') {
      await this.repository.update(
        {
          userId: data.userId,
          tenantId: ctx.tenantId,
          assignmentType: 'primary',
        },
        { assignmentType: 'secondary' }
      );
    }

    const assignment = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
      createdBy: ctx.userId,
    });

    return this.repository.save(assignment);
  }

  async update(
    ctx: ServiceContext,
    id: string,
    data: UpdateUserBranchAssignmentDto
  ): Promise<UserBranchAssignment | null> {
    const assignment = await this.findById(ctx, id);
    if (!assignment) {
      return null;
    }

    // If setting as primary, unset other primary assignments for this user
    if (data.assignmentType === 'primary' && assignment.assignmentType !== 'primary') {
      await this.repository.update(
        {
          userId: assignment.userId,
          tenantId: ctx.tenantId,
          assignmentType: 'primary',
        },
        { assignmentType: 'secondary' }
      );
    }

    Object.assign(assignment, data);
    return this.repository.save(assignment);
  }

  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.delete({ id, tenantId: ctx.tenantId });
    return result.affected ? result.affected > 0 : false;
  }

  async deactivate(ctx: ServiceContext, id: string): Promise<UserBranchAssignment | null> {
    return this.update(ctx, id, { isActive: false });
  }

  async setAsPrimary(ctx: ServiceContext, id: string): Promise<UserBranchAssignment | null> {
    const assignment = await this.findById(ctx, id);
    if (!assignment) {
      return null;
    }

    // Unset other primary assignments for this user
    await this.repository.update(
      {
        userId: assignment.userId,
        tenantId: ctx.tenantId,
        assignmentType: 'primary',
      },
      { assignmentType: 'secondary' }
    );

    assignment.assignmentType = 'primary';
    return this.repository.save(assignment);
  }

  async getBranchManagers(ctx: ServiceContext, branchId: string): Promise<UserBranchAssignment[]> {
    return this.repository.find({
      where: {
        branchId,
        tenantId: ctx.tenantId,
        branchRole: 'manager',
        isActive: true,
      },
      relations: ['branch'],
    });
  }

  async getUserPermissionsForBranch(
    ctx: ServiceContext,
    userId: string,
    branchId: string
  ): Promise<string[]> {
    const assignments = await this.repository.find({
      where: {
        userId,
        branchId,
        tenantId: ctx.tenantId,
        isActive: true,
      },
    });

    // Merge all permissions from all assignments
    const permissions = new Set<string>();
    assignments.forEach(a => {
      (a.permissions || []).forEach(p => permissions.add(p));
    });

    return Array.from(permissions);
  }

  async getAssignmentStatistics(ctx: ServiceContext): Promise<{
    totalAssignments: number;
    activeAssignments: number;
    byType: Record<string, number>;
    byRole: Record<string, number>;
  }> {
    const assignments = await this.repository.find({
      where: { tenantId: ctx.tenantId },
    });

    const byType: Record<string, number> = {};
    const byRole: Record<string, number> = {};

    assignments.forEach(a => {
      byType[a.assignmentType] = (byType[a.assignmentType] || 0) + 1;
      if (a.branchRole) {
        byRole[a.branchRole] = (byRole[a.branchRole] || 0) + 1;
      }
    });

    return {
      totalAssignments: assignments.length,
      activeAssignments: assignments.filter(a => a.isActive).length,
      byType,
      byRole,
    };
  }

  async transferUserToBranch(
    ctx: ServiceContext,
    userId: string,
    fromBranchId: string,
    toBranchId: string,
    transferPrimary: boolean = true
  ): Promise<UserBranchAssignment | null> {
    // Deactivate assignments at old branch
    await this.repository.update(
      {
        userId,
        branchId: fromBranchId,
        tenantId: ctx.tenantId,
      },
      { isActive: false }
    );

    // Create new assignment at new branch
    return this.create(ctx, {
      userId,
      branchId: toBranchId,
      assignmentType: transferPrimary ? 'primary' : 'secondary',
      isActive: true,
    });
  }
}
