package com.bcxin.tenant.domain.entities;

import com.bcxin.Infrastructures.components.JsonProvider;
import com.bcxin.Infrastructures.entities.EntityAbstract;
import com.bcxin.Infrastructures.entities.IAggregate;
import com.bcxin.Infrastructures.enums.MasterSlaveType;
import com.bcxin.Infrastructures.exceptions.BadTenantException;
import com.bcxin.Infrastructures.utils.UUIDUtil;
import com.bcxin.tenant.domain.DomainConstraint;
import com.bcxin.Infrastructures.enums.DepartImPermissionType;
import com.bcxin.Infrastructures.enums.OnlineOfflineStatus;
import com.bcxin.tenant.domain.events.DepartmentCreatedEvent;
import com.bcxin.tenant.domain.snapshots.DepartImAllowedDepartSnapshot;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.StringUtils;

import javax.persistence.*;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;

@Getter
@Setter(AccessLevel.PROTECTED)
@Table(name = "tenant_departments")
@Entity
public class DepartmentEntity extends EntityAbstract implements IAggregate {
    @Id
    private String id;

    @Column(name = "code", nullable = false, length = 50)
    private String code;

    @Column(nullable = false, length = 200)
    private String name;

    @Column(name = "display_order", nullable = false)
    private int displayOrder;

    @Column(nullable = false, name = "created_time")
    private Timestamp createdTime;

    @Column(nullable = true, name = "last_updated_time")
    private Timestamp lastUpdatedTime;

    @Column(name = "total_member", nullable = false)
    private int totalMember;

    /**
     * 索引树, 提高检索速度
     */
    @Column(name = "index_tree", nullable = false, length = 2000)
    private String indexTree;

    @Column(name = "level", nullable = false)
    private int level;

    /**
     * 是否有效
     */
    @Column(name = "status", nullable = false)
    @Enumerated(EnumType.ORDINAL)
    public OnlineOfflineStatus status;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id", referencedColumnName = "id",
            foreignKey = @ForeignKey(name = "fk_tenant_depart_parent_id", value = ConstraintMode.CONSTRAINT))
    private DepartmentEntity parent;

    @OneToMany(mappedBy = "parent", cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE}, fetch = FetchType.LAZY)
    private Collection<DepartmentEntity> children;

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "organization_id", referencedColumnName = "id", nullable = false,
            foreignKey = @ForeignKey(name = "fk_tenant_depart_organization_id", value = ConstraintMode.CONSTRAINT))
    private OrganizationEntity organization;

    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
    private Collection<DepartmentEmployeeRelationEntity> departmentEmployeeRelations;

    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
    private Collection<DepartmentAdminEntity> departmentAdmins;

    @Enumerated
    @Column(nullable = false, name = "permission_type")
    private DepartImPermissionType permissionType;

    @Column(name = "permission_config", length = 2000)
    private String permissionConfig;

    /**删除标识*/
    @Column(nullable = false, name = "is_deleted")
    private boolean deleted = false;

    public DepartmentEntity addChild(String name, int displayOrder) {
        Collection<DepartmentEntity> departmentEntities = this.getChildren();
        if (departmentEntities == null) {
            departmentEntities = new ArrayList<>();
        }

        Optional<DepartmentEntity> departmentOptional = departmentEntities.stream().filter(ii -> ii.getName().equals(name)).findFirst();
        if (departmentOptional.isPresent()) {
            departmentOptional.get().change(name, displayOrder);

            return departmentOptional.get();
        } else {
            DepartmentEntity department = DepartmentEntity.create(this.getOrganization(),this, name, null, displayOrder);
            departmentEntities.add(department);

            this.setChildren(departmentEntities);

            return department;
        }
    }


    public void change(String name, int displayOrder) {
        this.setName(name);
        this.setDisplayOrder(displayOrder);
        this.setLastUpdatedTime(Timestamp.from(Instant.now()));
    }

    public void change(DepartImPermissionType permissionType, DepartImAllowedDepartSnapshot snapshot,
                       JsonProvider jsonProvider) {
        this.setPermissionType(permissionType);
        this.setPermissionConfig(jsonProvider.getJson(snapshot));

        this.setLastUpdatedTime(Timestamp.from(Instant.now()));
    }

    public void changeStatus(OnlineOfflineStatus status) {
        this.setStatus(status);
        this.setLastUpdatedTime(Timestamp.from(Instant.now()));
    }

    public void changeSubNodeParent(DepartmentEntity department) {
        if (department.getIndexTree().startsWith(this.getIndexTree())) {
            throw new BadTenantException(String.format("不能将其子部门(%s)设置为该部门的父部门", department.getName()));
        }

        this.changeParent(department);
    }

    private void changeParent(DepartmentEntity department) {
        if (department == null) {
            return;
        }

        this.setParent(department);
        String indexTree = String.format("%s-%s", department.getIndexTree(), this.getId());
        this.setIndexTree(indexTree);

        this.setLevel(department.getLevel() + 1);
        this.setLastUpdatedTime(Timestamp.from(Instant.now()));
    }

    /**
     * 把本部门人员转移到指定部门
     * @param destDepartment
     */
    public void moveEmployeesTo(DepartmentEntity destDepartment) {
/*
        Collection<DepartmentEmployeeRelationEntity> departmentEmployeeRelations =
                this.getDepartmentEmployeeRelations().stream().collect(Collectors.toList());
        departmentEmployeeRelations.forEach(ix -> {
            ix.getDepartment().declEmployee();
            ix.getEmployee().joinDepartment(destDepartment, MasterSlaveType.Normal);
            this.getDepartmentEmployeeRelations().remove(ix);
        });

        this.setLastUpdatedTime(Timestamp.from(Instant.now()));

 */
        this.getDepartmentEmployeeRelations().forEach(ix -> {
            ix.gotoDepartment(destDepartment);
        });

        this.setLastUpdatedTime(Timestamp.from(Instant.now()));
    }

    /**
     * 从哪些部门转移人员到本部门
     * @param sourceDepartments
     */
    public void moveEmployeesFrom(Collection<DepartmentEntity> sourceDepartments) {
        for (DepartmentEntity department : sourceDepartments) {
            department.moveEmployeesTo(this);
        }

        this.setLastUpdatedTime(Timestamp.from(Instant.now()));
    }

    public void incrEmployee() {
        this.setTotalMember(this.getTotalMember() + 1);
    }

    public void declEmployee() {
        this.setTotalMember(this.getTotalMember() - 1);
    }

    /**
     * 添加部门以及人员的关系
     * 丢弃该功能，这个会引发性能问题
     */
    /*
    public void addEmployeeRelation(EmployeeEntity employee, MasterSlaveType masterSlaveType) {
        Collection<DepartmentEmployeeRelationEntity> departmentEmployeeRelationEntities = this.getDepartmentEmployeeRelations();
        if (departmentEmployeeRelationEntities == null) {
            departmentEmployeeRelationEntities = new ArrayList<>();
        }

        departmentEmployeeRelationEntities.add(DepartmentEmployeeRelationEntity.create(this, employee, masterSlaveType));

        this.setDepartmentEmployeeRelations(departmentEmployeeRelationEntities);
        this.setTotalMember(departmentEmployeeRelationEntities.size());
    }
     */

    protected DepartmentEntity() {
        this.setCreatedTime(Timestamp.from(Instant.now()));
        this.setDisplayOrder(0);
        this.setLevel(0);
        this.setStatus(OnlineOfflineStatus.Online);
    }

    public static DepartmentEntity createRoot(
            OrganizationEntity organization,
            String name, int displayOrder) {
        assert organization != null;

        DepartmentEntity department = new DepartmentEntity();
        department.setId(UUIDUtil.getShortUuid());
        department.setCode(department.getId());
        department.setName(name);
        department.setOrganization(organization);
        department.setDisplayOrder(displayOrder);
        department.setIndexTree(department.getId());
        department.setPermissionType(DepartImPermissionType.JustOnDepart);

        department.recordEvent(DepartmentCreatedEvent.create(department));

        return department;
    }

    public static DepartmentEntity create(
            OrganizationEntity organization,
            DepartmentEntity parent,
            String name,
            String code,
            int displayOrder) {
        assert organization != null;

        DepartmentEntity department = create(organization, name, code, displayOrder);
        department.changeParent(parent);
        return department;
    }

    private static DepartmentEntity create(OrganizationEntity organization,
                                           String name, String code, int displayOrder)
    {
        assert organization != null;

        DepartmentEntity department = new DepartmentEntity();
        department.setId(UUIDUtil.getShortUuid());
        if(!StringUtils.hasLength(code)) {
            code = department.getId();
        }

        department.setName(name);
        department.setCode(code);
        department.setOrganization(organization);
        department.setDisplayOrder(displayOrder);
        department.setIndexTree(department.getId());
        department.setPermissionType(DepartImPermissionType.JustOnDepart);

        department.recordEvent(DepartmentCreatedEvent.create(department));

        return department;
    }

    @Transient
    public String getFullPath() {
        StringBuilder fullDepartPathName = new StringBuilder();
        DepartmentEntity loopDepartment = this;
        int index = 0;
        while (loopDepartment != null) {
            if (index > 0) {
                fullDepartPathName.insert(0, "/");
            }

            fullDepartPathName.insert(0, loopDepartment.getName());

            index++;
            loopDepartment = loopDepartment.getParent();

            if (index > 100) {
                break;
            }
        }

        return fullDepartPathName.toString();
    }

    public void resetIndexTree(String indexTree){
        this.setIndexTree(indexTree);
    }
}






