package br.gov.sigefp.org.service;

import br.gov.sigefp.org.api.dto.OrgUnitDTO;
import br.gov.sigefp.org.domain.Ministry;
import br.gov.sigefp.org.domain.OrgUnit;
import br.gov.sigefp.org.repository.MinistryRepository;
import br.gov.sigefp.org.repository.OrgUnitRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Serviço de aplicação para gestão de unidades organizacionais.
 */
@Service
@RequiredArgsConstructor
@Transactional
public class OrgUnitService {

    private final OrgUnitRepository orgUnitRepository;
    private final MinistryRepository ministryRepository;
    private final br.gov.sigefp.common.service.GlobalAuditLogService auditLogService;

    @CacheEvict(value = { "orgUnits", "orgUnitsByMinistry" }, allEntries = true)
    public OrgUnitDTO create(OrgUnitDTO dto) {
        // Validação: unicidade de código
        if (orgUnitRepository.existsByCode(dto.getCode())) {
            throw new IllegalArgumentException("Código de unidade já existe: " + dto.getCode());
        }

        Ministry ministry = ministryRepository.findById(dto.getMinistryId())
                .orElseThrow(() -> new IllegalArgumentException("Ministério não encontrado: " + dto.getMinistryId()));

        OrgUnit parentUnit = null;
        if (dto.getParentUnitId() != null) {
            parentUnit = orgUnitRepository.findById(dto.getParentUnitId())
                    .orElseThrow(
                            () -> new IllegalArgumentException("Unidade pai não encontrada: " + dto.getParentUnitId()));

            // Validação: parentUnit deve pertencer ao mesmo ministério
            if (!parentUnit.getMinistry().getId().equals(ministry.getId())) {
                throw new IllegalArgumentException("Unidade pai deve pertencer ao mesmo ministério");
            }
        }

        OrgUnit orgUnit = OrgUnit.builder()
                .ministry(ministry)
                .parentUnit(parentUnit)
                .code(dto.getCode())
                .name(dto.getName())
                .unitType(dto.getType())
                .acronym(dto.getAcronym())
                .address(dto.getAddress())
                .email(dto.getEmail())
                .phone(dto.getPhone())
                .isActive(dto.getIsActive() != null ? dto.getIsActive() : true)
                .build();

        OrgUnit saved = orgUnitRepository.save(orgUnit);
        return toDTO(saved);
    }

    @CacheEvict(value = { "orgUnits", "orgUnitsByMinistry" }, allEntries = true)
    public OrgUnitDTO update(UUID id, OrgUnitDTO dto) {
        OrgUnit orgUnit = orgUnitRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Unidade organizacional não encontrada: " + id));

        StringBuilder log = new StringBuilder();

        if (dto.getCode() != null && !dto.getCode().equals(orgUnit.getCode())) {
            if (orgUnitRepository.existsByCode(dto.getCode())) {
                throw new IllegalArgumentException("Código de unidade já existe: " + dto.getCode());
            }
            log.append("Código: [").append(orgUnit.getCode()).append("] -> [").append(dto.getCode()).append("]. ");
            orgUnit.setCode(dto.getCode());
        }

        if (dto.getMinistryId() != null && !dto.getMinistryId().equals(orgUnit.getMinistry().getId())) {
            Ministry ministry = ministryRepository.findById(dto.getMinistryId())
                    .orElseThrow(
                            () -> new IllegalArgumentException("Ministério não encontrado: " + dto.getMinistryId()));
            log.append("Ministério: [").append(orgUnit.getMinistry().getName()).append("] -> [")
                    .append(ministry.getName()).append("]. ");
            orgUnit.setMinistry(ministry);
        }

        if (dto.getParentUnitId() != null) {
            if (orgUnit.getParentUnit() == null || !dto.getParentUnitId().equals(orgUnit.getParentUnit().getId())) {
                OrgUnit parentUnit = orgUnitRepository.findById(dto.getParentUnitId())
                        .orElseThrow(() -> new IllegalArgumentException(
                                "Unidade pai não encontrada: " + dto.getParentUnitId()));

                if (!parentUnit.getMinistry().getId().equals(orgUnit.getMinistry().getId())) {
                    throw new IllegalArgumentException("Unidade pai deve pertencer ao mesmo ministério");
                }
                String oldParent = orgUnit.getParentUnit() != null ? orgUnit.getParentUnit().getName() : "N/A";
                log.append("Unidade Pai: [").append(oldParent).append("] -> [").append(parentUnit.getName())
                        .append("]. ");
                orgUnit.setParentUnit(parentUnit);
            }
        } else if (dto.getParentUnitId() == null && orgUnit.getParentUnit() != null) {
            log.append("Unidade Pai: [").append(orgUnit.getParentUnit().getName()).append("] -> [N/A]. ");
            orgUnit.setParentUnit(null);
        }

        if (dto.getName() != null && !dto.getName().equals(orgUnit.getName())) {
            log.append("Nome: [").append(orgUnit.getName()).append("] -> [").append(dto.getName()).append("]. ");
            orgUnit.setName(dto.getName());
        }
        if (dto.getType() != null && !dto.getType().equals(orgUnit.getUnitType())) {
            log.append("Tipo: [").append(orgUnit.getUnitType()).append("] -> [").append(dto.getType()).append("]. ");
            orgUnit.setUnitType(dto.getType());
        }
        if (dto.getAcronym() != null && !dto.getAcronym().equals(orgUnit.getAcronym())) {
            log.append("Sigla: [").append(orgUnit.getAcronym()).append("] -> [").append(dto.getAcronym()).append("]. ");
            orgUnit.setAcronym(dto.getAcronym());
        }
        if (dto.getIsActive() != null && !dto.getIsActive().equals(orgUnit.getIsActive())) {
            log.append("Ativo: [").append(orgUnit.getIsActive()).append("] -> [").append(dto.getIsActive())
                    .append("]. ");
            orgUnit.setIsActive(dto.getIsActive());
        }

        // Outros campos técnicos (contato/endereço) - log simplificado
        if (dto.getEmail() != null && !dto.getEmail().equals(orgUnit.getEmail()))
            orgUnit.setEmail(dto.getEmail());
        if (dto.getPhone() != null && !dto.getPhone().equals(orgUnit.getPhone()))
            orgUnit.setPhone(dto.getPhone());
        if (dto.getAddress() != null && !dto.getAddress().equals(orgUnit.getAddress()))
            orgUnit.setAddress(dto.getAddress());

        OrgUnit saved = orgUnitRepository.save(orgUnit);

        if (log.length() > 0) {
            // Nota: id do usuário viria do SecurityContext em produção
            auditLogService.logAction(null, "system", "ORG", "UPDATE", "OrgUnit", id, log.toString());
        }

        return toDTO(saved);
    }

    @Transactional(readOnly = true)
    @Cacheable(value = "orgUnits", key = "#id")
    public OrgUnitDTO findById(UUID id) {
        OrgUnit orgUnit = orgUnitRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Unidade organizacional não encontrada: " + id));
        return toDTO(orgUnit);
    }

    @Transactional(readOnly = true)
    public Page<OrgUnitDTO> findAll(Pageable pageable) {
        return orgUnitRepository.findAll(pageable).map(this::toDTO);
    }

    @Transactional(readOnly = true)
    @Cacheable(value = "orgUnitsByMinistry", key = "#ministryId")
    public List<OrgUnitDTO> findByMinistryId(UUID ministryId) {
        return orgUnitRepository.findByMinistryId(ministryId).stream()
                .map(this::toDTO)
                .collect(Collectors.toList());
    }

    @Transactional(readOnly = true)
    public List<OrgUnitDTO> findTreeByMinistryId(UUID ministryId) {
        // Buscar unidades raiz (sem parentUnit) do ministério
        List<OrgUnit> rootUnits = orgUnitRepository.findRootUnitsByMinistryId(ministryId);

        // Construir árvore recursivamente
        return rootUnits.stream()
                .map(this::buildTree)
                .collect(Collectors.toList());
    }

    private OrgUnitDTO buildTree(OrgUnit orgUnit) {
        OrgUnitDTO dto = toDTO(orgUnit);

        // Buscar filhos
        List<OrgUnit> children = orgUnitRepository.findByParentUnitId(orgUnit.getId());
        if (!children.isEmpty()) {
            List<OrgUnitDTO> childrenDTOs = children.stream()
                    .map(this::buildTree)
                    .collect(Collectors.toList());
            dto.setChildren(childrenDTOs);
        } else {
            dto.setChildren(new ArrayList<>());
        }

        return dto;
    }

    private OrgUnitDTO toDTO(OrgUnit orgUnit) {
        return OrgUnitDTO.builder()
                .id(orgUnit.getId())
                .ministryId(orgUnit.getMinistry().getId())
                .parentUnitId(orgUnit.getParentUnit() != null ? orgUnit.getParentUnit().getId() : null)
                .code(orgUnit.getCode())
                .name(orgUnit.getName())
                .type(orgUnit.getUnitType())
                .acronym(orgUnit.getAcronym())
                .address(orgUnit.getAddress())
                .email(orgUnit.getEmail())
                .phone(orgUnit.getPhone())
                .isActive(orgUnit.getIsActive())
                .build();
    }
}
