package kr.co.accura.wtm.domain.project.service;

import kr.co.accura.wbx.spring.common.BusinessException;
import kr.co.accura.wbx.spring.common.NotFoundException;
import kr.co.accura.wtm.domain.project.ProjectStatus;
import kr.co.accura.wtm.domain.project.ProjectType;
import kr.co.accura.wtm.domain.project.dto.*;
import kr.co.accura.wtm.domain.project.entity.Project;
import kr.co.accura.wtm.domain.project.entity.ProjectAssignment;
import kr.co.accura.wtm.domain.project.repository.ProjectAssignmentRepository;
import kr.co.accura.wtm.domain.project.repository.ProjectRepository;
import kr.co.accura.wtm.domain.user.entity.User;
import kr.co.accura.wtm.domain.user.repository.UserRepository;
import lombok.RequiredArgsConstructor;
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.List;

@Service
@Transactional
@RequiredArgsConstructor
public class ProjectService {

    private final ProjectRepository projectRepository;
    private final ProjectAssignmentRepository assignmentRepository;
    private final UserRepository userRepository;

    @Transactional(readOnly = true)
    public Page<ProjectDto> findAll(Pageable pageable) {
        return projectRepository.findAll(pageable).map(ProjectDto::from);
    }

    @Transactional(readOnly = true)
    public ProjectDto findById(Long id) {
        Project project = projectRepository.findById(id)
                .orElseThrow(() -> new NotFoundException("Project not found: " + id));
        return ProjectDto.from(project);
    }

    public ProjectDto create(ProjectCreateRequest request) {
        if (projectRepository.existsByProjectCode(request.projectCode())) {
            throw new BusinessException("Project code already exists: " + request.projectCode());
        }

        Project project = Project.builder()
                .projectCode(request.projectCode())
                .name(request.name())
                .description(request.description())
                .projectType(ProjectType.valueOf(request.projectType()))
                .startDate(request.startDate())
                .endDate(request.endDate())
                .build();

        if (request.pmUserId() != null) {
            User pmUser = userRepository.findById(request.pmUserId())
                    .orElseThrow(() -> new NotFoundException("PM user not found: " + request.pmUserId()));
            project.setPmUser(pmUser);
        }

        projectRepository.save(project);
        return ProjectDto.from(project);
    }

    public ProjectDto update(Long id, ProjectCreateRequest request) {
        Project project = projectRepository.findById(id)
                .orElseThrow(() -> new NotFoundException("Project not found: " + id));

        if (request.name() != null) project.setName(request.name());
        if (request.description() != null) project.setDescription(request.description());
        if (request.projectType() != null) project.setProjectType(ProjectType.valueOf(request.projectType()));
        if (request.startDate() != null) project.setStartDate(request.startDate());
        if (request.endDate() != null) project.setEndDate(request.endDate());
        if (request.pmUserId() != null) {
            User pmUser = userRepository.findById(request.pmUserId())
                    .orElseThrow(() -> new NotFoundException("PM user not found: " + request.pmUserId()));
            project.setPmUser(pmUser);
        }

        projectRepository.save(project);
        return ProjectDto.from(project);
    }

    @Transactional(readOnly = true)
    public List<ProjectDto> findMyProjects(Long userId) {
        List<ProjectAssignment> assignments = assignmentRepository.findByUser_Id(userId);
        return assignments.stream()
                .map(a -> ProjectDto.from(a.getProject()))
                .toList();
    }

    // --- Assignment methods ---

    @Transactional(readOnly = true)
    public List<ProjectAssignmentDto> getAssignments(Long projectId) {
        return assignmentRepository.findByProject_Id(projectId).stream()
                .map(ProjectAssignmentDto::from)
                .toList();
    }

    public ProjectAssignmentDto createAssignment(Long projectId, AssignmentCreateRequest request) {
        Project project = projectRepository.findById(projectId)
                .orElseThrow(() -> new NotFoundException("Project not found: " + projectId));
        User user = userRepository.findById(request.userId())
                .orElseThrow(() -> new NotFoundException("User not found: " + request.userId()));

        if (assignmentRepository.existsByProject_IdAndUser_Id(projectId, request.userId())) {
            throw new BusinessException("User already assigned to this project");
        }

        ProjectAssignment assignment = ProjectAssignment.builder()
                .project(project)
                .user(user)
                .role(request.role())
                .build();

        assignmentRepository.save(assignment);
        return ProjectAssignmentDto.from(assignment);
    }

    public ProjectAssignmentDto updateAssignment(Long projectId, Long assignId, AssignmentCreateRequest request) {
        ProjectAssignment assignment = assignmentRepository.findById(assignId)
                .orElseThrow(() -> new NotFoundException("Assignment not found: " + assignId));

        if (request.role() != null) assignment.setRole(request.role());

        assignmentRepository.save(assignment);
        return ProjectAssignmentDto.from(assignment);
    }

    public void deleteAssignment(Long projectId, Long assignId) {
        ProjectAssignment assignment = assignmentRepository.findById(assignId)
                .orElseThrow(() -> new NotFoundException("Assignment not found: " + assignId));
        assignmentRepository.delete(assignment);
    }

    @Transactional(readOnly = true)
    public Page<User> getAvailableUsers(Long projectId, Pageable pageable) {
        // Returns all active users; filtering out already-assigned users can be refined later
        return userRepository.findAll(pageable);
    }
}
