package com.ikon.projectmanagement.service.impl;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.ikon.projectmanagement.dto.request.ProductOfProjectRequestDto;
import com.ikon.projectmanagement.dto.response.ProductExpenseResponseDto;
import com.ikon.projectmanagement.dto.response.ProductOfProjectResponseDto;
import com.ikon.projectmanagement.entity.ProductOfProject;
import com.ikon.projectmanagement.entity.ProductOfProjectExpenseDetail;
import com.ikon.projectmanagement.mapper.ProductOfProjectMapper;
import com.ikon.projectmanagement.repository.ProductOfProjectRepository;
import com.ikon.projectmanagement.repository.ProjectScheduleRepository;
import com.ikon.projectmanagement.service.ProductOfProjectService;
import com.ikon.projectmanagement.service.ScheduleService;
import com.ikon.webservice.WebService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class ProductOfProjectServiceImpl extends WebService implements ProductOfProjectService {

        private final ProductOfProjectMapper productMapper;
        private final ProductOfProjectRepository productRepository;
        private final ScheduleService scheduleService;
        private final ProjectScheduleRepository projectScheduleRepository;

        // Valid workflow transitions: currentStatus -> Set of allowed targetStatuses
        private static final Map<String, Set<String>> VALID_TRANSITIONS = Map.of(
                        // Forward transitions
                        "Product Created", Set.of("Schedule Submitted From Product"),
                        "Schedule Submitted From Product", Set.of("Submitted Resources and Expenses For Product",
                                        "Recall Schedule from Resource & Expense"),
                        // "Submitted Resources and Expenses For Product", Set.of("Closed",
                        // "Recall Schedule from Resource & Expense"),
                        // "Closed", Set.of("Recall Resources & Expenses from Closed"),

                        // Also allow transitions from recall statuses
                        // "Recall Product Created from Schedule", Set.of("Schedule Submitted From
                        // Product"),
                        "Recall Schedule from Resource & Expense", Set.of("Schedule Submitted From Product")
        // "Recall Resources & Expenses from Closed", Set.of("Closed",
        // "Recall Schedule from Resource & Expense")
        );

        // The target status that requires schedule data to exist
        private static final String REQUIRES_SCHEDULE = "Schedule Submitted From Product";

        @Override
        @Transactional(readOnly = true)
        public List<ProductOfProjectResponseDto> getProductsByProject(UUID projectIdentifier) {

                UUID accountId = getActiveAccountId();
                var scheduleData = scheduleService.getSchedule(projectIdentifier, null);

                return productRepository
                                .findByAccountIdAndProjectIdentifier(accountId, projectIdentifier)
                                .stream()
                                .map(p -> {
                                        ProductOfProjectResponseDto dto = productMapper.toResponse(p);
                                        dto.setScheduleData(scheduleData);
                                        return dto;
                                })
                                .toList();
        }

        @Override
        @Transactional(readOnly = true)
        public ProductOfProjectResponseDto getProduct(UUID projectIdentifier, UUID productIdentifier) {

                UUID accountId = getActiveAccountId();

                ProductOfProject product = productRepository
                                .findByAccountIdAndProjectIdentifierAndProductIdentifier(
                                                accountId, projectIdentifier, productIdentifier)
                                .orElseThrow(() -> new RuntimeException("Product not found"));

                ProductOfProjectResponseDto dto = productMapper.toResponse(product);
                dto.setScheduleData(scheduleService.getSchedule(projectIdentifier, null));
                return dto;
        }

        @Override
        @Transactional
        public ProductOfProjectResponseDto updateProduct(UUID projectIdentifier, UUID productIdentifier,
                        ProductOfProjectRequestDto dto) {
                UUID accountId = getActiveAccountId();

                ProductOfProject product = productRepository
                                .findByAccountIdAndProjectIdentifierAndProductIdentifier(accountId, projectIdentifier,
                                                productIdentifier)
                                .orElseThrow(() -> new RuntimeException("Product not found"));

                productMapper.updateEntity(product, dto);

                if (dto.getExpenseDetails() != null) {
                        Map<UUID, ProductOfProjectExpenseDetail> existingExpenses = product.getExpenseDetails();
                        Map<UUID, ProductExpenseResponseDto> incomingExpenses = dto.getExpenseDetails();

                        existingExpenses.keySet().removeIf(id -> !incomingExpenses.containsKey(id));

                        for (Map.Entry<UUID, ProductExpenseResponseDto> entry : incomingExpenses.entrySet()) {
                                UUID incomingId = entry.getKey();
                                ProductExpenseResponseDto expenseDto = entry.getValue();

                                if (incomingId != null && existingExpenses.containsKey(incomingId)) {
                                        ProductOfProjectExpenseDetail existing = existingExpenses.get(incomingId);
                                        productMapper.updateExpenseEntity(existing, expenseDto);
                                } else {
                                        ProductOfProjectExpenseDetail newExpense = productMapper
                                                        .toExpenseEntity(expenseDto);
                                        newExpense.setProductPS(product);

                                        existingExpenses.put(UUID.randomUUID(), newExpense);
                                }
                        }
                }

                ProductOfProject saved = productRepository.save(product);
                return productMapper.toResponse(saved);
        }

        @Override
        @Transactional
        public ProductOfProjectResponseDto transitionStatus(
                        UUID projectIdentifier,
                        UUID productIdentifier,
                        String targetStatus) {

                UUID accountId = getActiveAccountId();

                ProductOfProject product = productRepository
                                .findByAccountIdAndProjectIdentifierAndProductIdentifier(
                                                accountId, projectIdentifier, productIdentifier)
                                .orElseThrow(() -> new RuntimeException("Product not found"));

                String currentStatus = product.getProductStatus();
                log.info("Workflow transition requested: '{}' -> '{}' for product {}",
                                currentStatus, targetStatus, productIdentifier);

                // Validate that this transition is allowed
                Set<String> allowedTargets = VALID_TRANSITIONS.get(currentStatus);
                log.info("Allowed target statuses for current status '{}': {}", currentStatus, allowedTargets);
                if (allowedTargets == null || !allowedTargets.contains(targetStatus)) {
                        throw new IllegalStateException(
                                        "Transition from '" + currentStatus + "' to '" + targetStatus
                                                        + "' is not allowed.");
                }

                // Special validation: Schedule -> Resources & Expenses requires schedule data
                if (REQUIRES_SCHEDULE.equals(targetStatus)) {
                        boolean hasScheduleData = checkScheduleDataExists(projectIdentifier, productIdentifier,
                                        accountId);
                        if (!hasScheduleData) {
                                throw new IllegalStateException(
                                                "Cannot move to Resources & Expenses: No schedule data found. "
                                                                + "Please create a schedule before proceeding.");
                        }
                }

                // Perform the transition
                product.setProductStatus(targetStatus);
                ProductOfProject saved = productRepository.save(product);

                log.info("Workflow transition completed: '{}' -> '{}' for product {}",
                                currentStatus, targetStatus, productIdentifier);

                return productMapper.toResponse(saved);
        }

        /**
         * Checks if schedule data exists by looking up:
         * 1. Project schedule (by projectIdentifier)
         * 2. Product schedule data (by productIdentifier — stored on the product
         * entity)
         * If either has tasks, returns true.
         */
        private boolean checkScheduleDataExists(UUID projectIdentifier, UUID productIdentifier, UUID accountId) {
                // Check 1: Project-level schedule
                boolean hasProjectSchedule = projectScheduleRepository
                                .findByAccountIdAndProjectIdentifier(accountId, projectIdentifier)
                                .map(schedule -> schedule.getTasks() != null && !schedule.getTasks().isEmpty())
                                .orElse(false);

                if (hasProjectSchedule) {
                        log.debug("Schedule data found via project schedule for project {}", projectIdentifier);
                        return true;
                }

                // Check 2: Product-level schedule data
                boolean hasProductSchedule = projectScheduleRepository
                                .findByAccountIdAndProductIdentifier(accountId, productIdentifier)
                                .map(schedule -> schedule.getTasks() != null && !schedule.getTasks().isEmpty())
                                .orElse(false);

                if (hasProductSchedule) {
                        log.debug("Schedule data found via product schedule for product {}", productIdentifier);
                        return true;
                }

                log.warn("No schedule data found for project {} or product {}", projectIdentifier, productIdentifier);
                return false;
        }

}
