
package cn.novalon.gym.manage.groupcourse.repository.impl;

import cn.novalon.gym.manage.common.dto.PageRequest;
import cn.novalon.gym.manage.common.dto.PageResponse;
import cn.novalon.gym.manage.groupcourse.converter.GroupCourseConverter;
import cn.novalon.gym.manage.groupcourse.dao.GroupCourseDao;
import cn.novalon.gym.manage.groupcourse.domain.GroupCourse;
import cn.novalon.gym.manage.groupcourse.dto.GroupCourseQueryDto;
import cn.novalon.gym.manage.groupcourse.entity.GroupCourseEntity;
import cn.novalon.gym.manage.groupcourse.repository.IGroupCourseRepository;
import org.springframework.data.domain.Sort;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.relational.core.query.Query;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.time.LocalDateTime;
import java.util.List;

@Repository
public class GroupCourseRepository implements IGroupCourseRepository {
    private final GroupCourseDao groupCourseDao;
    private final GroupCourseConverter groupCourseConverter;
    private final R2dbcEntityTemplate r2dbcEntityTemplate;

    public GroupCourseRepository(GroupCourseDao groupCourseDao, GroupCourseConverter groupCourseConverter,
                                  R2dbcEntityTemplate r2dbcEntityTemplate){
        this.groupCourseDao = groupCourseDao;
        this.groupCourseConverter = groupCourseConverter;
        this.r2dbcEntityTemplate = r2dbcEntityTemplate;
    }

    @Override
    public Mono<GroupCourse> findByIdAndDeletedAtIsNull(Long id) {
        return groupCourseDao.findByIdIsAndDeletedAtIsNull(id)
                .map(groupCourseConverter::toDomain);
    }

    @Override
    public Flux<GroupCourse> findAll() {
        return groupCourseDao.findAll()
                .map(groupCourseConverter::toDomain);
    }

    @Override
    public Flux<GroupCourse> findAll(Sort sort) {
        return groupCourseDao.findAll(sort)
                .map(groupCourseConverter::toDomain);
    }

    @Override
    public Flux<GroupCourse> findByDeletedAtIsNull() {
        return groupCourseDao.findAllByDeletedAtIsNull()
                .map(groupCourseConverter::toDomain);
    }

    @Override
    public Flux<GroupCourse> findByDeletedAtIsNull(Sort sort) {
        return groupCourseDao.findAllByDeletedAtIsNull(sort)
                .map(groupCourseConverter::toDomain);
    }

    @Override
    public Mono<PageResponse<GroupCourse>> findByPage(PageRequest pageRequest) {
        int page = pageRequest.getPage();
        int size = pageRequest.getSize();
        String sort = pageRequest.getSort();
        String order = pageRequest.getOrder();

        Sort sortObj = Sort.unsorted();
        if (sort != null && !sort.isEmpty()) {
            sortObj = Sort.by(Sort.Direction.fromString(order), sort);
        }

        org.springframework.data.domain.PageRequest pageable = org.springframework.data.domain.PageRequest.of(page, size, sortObj);

        Query query = Query.empty();

        return r2dbcEntityTemplate.select(GroupCourseEntity.class)
                .matching(query.with(pageable))
                .all()
                .collectList()
                .zipWith(r2dbcEntityTemplate.count(query, GroupCourseEntity.class))
                .map(tuple -> {
                    long total = tuple.getT2();
                    int totalPages = (int) Math.ceil((double) total / size);
                    List<GroupCourse> courseList = tuple.getT1().stream()
                            .map(groupCourseConverter::toDomain)
                            .toList();
                    return new PageResponse<>(courseList, totalPages, total, page, size);
                });
    }

    @Override
    public Mono<PageResponse<GroupCourse>> findByPageAndNotDeleted(PageRequest pageRequest) {
        int page = pageRequest.getPage();
        int size = pageRequest.getSize();
        String sort = pageRequest.getSort();
        String order = pageRequest.getOrder();

        Sort sortObj = Sort.unsorted();
        if (sort != null && !sort.isEmpty()) {
            sortObj = Sort.by(Sort.Direction.fromString(order), sort);
        }

        org.springframework.data.domain.PageRequest pageable = org.springframework.data.domain.PageRequest.of(page, size, sortObj);

        return groupCourseDao.findAllByDeletedAtIsNull(sortObj)
                .collectList()
                .zipWith(groupCourseDao.findAllByDeletedAtIsNull().count())
                .map(tuple -> {
                    List<GroupCourseEntity> allEntities = tuple.getT1();
                    long total = tuple.getT2();
                    
                    int fromIndex = page * size;
                    int toIndex = Math.min(fromIndex + size, allEntities.size());
                    
                    List<GroupCourse> courseList;
                    if (fromIndex < allEntities.size()) {
                        courseList = allEntities.subList(fromIndex, toIndex).stream()
                                .map(groupCourseConverter::toDomain)
                                .toList();
                    } else {
                        courseList = List.of();
                    }
                    
                    int totalPages = (int) Math.ceil((double) total / size);
                    return new PageResponse<>(courseList, totalPages, total, page, size);
                });
    }

    @Override
    public Mono<GroupCourse> save(GroupCourse groupCourse) {
        GroupCourseEntity entity = groupCourseConverter.toEntity(groupCourse);
        entity.setCreatedAt(LocalDateTime.now());
        entity.setUpdatedAt(LocalDateTime.now());
        entity.setStatus(0L);
        entity.setCurrentMembers(0);
        
        return groupCourseDao.save(entity)
                .map(groupCourseConverter::toDomain);
    }

    @Override
    public Mono<GroupCourse> update(GroupCourse groupCourse) {
        GroupCourseEntity entity = groupCourseConverter.toEntity(groupCourse);
        entity.setUpdatedAt(LocalDateTime.now());
        
        return r2dbcEntityTemplate.update(entity)
                .then(findByIdAndDeletedAtIsNull(groupCourse.getId()));
    }

    @Override
    public Mono<GroupCourse> cancel(Long id) {
        return groupCourseDao.cancelCourse(id, LocalDateTime.now())
                .flatMap(updated -> {
                    if (updated > 0) {
                        return findByIdAndDeletedAtIsNull(id);
                    }
                    return Mono.empty();
                });
    }

    @Override
    public Mono<Void> deleteById(Long id) {
        return groupCourseDao.softDelete(id, LocalDateTime.now())
                .then();
    }

    @Override
    public Mono<GroupCourse> updateCurrentMembers(Long id, Integer delta) {
        return groupCourseDao.updateCurrentMembers(id, delta, LocalDateTime.now())
                .flatMap(updated -> {
                    if (updated > 0) {
                        return findByIdAndDeletedAtIsNull(id);
                    }
                    return Mono.empty();
                });
    }

    @Override
    public Flux<GroupCourse> findByCourseType(Long courseType) {
        return groupCourseDao.findByCourseTypeAndDeletedAtIsNull(courseType)
                .map(groupCourseConverter::toDomain);
    }

    @Override
    public Mono<PageResponse<GroupCourse>> searchGroupCourses(GroupCourseQueryDto query) {
        return groupCourseDao.countSearchGroupCourses(r2dbcEntityTemplate.getDatabaseClient(), query)
                .flatMap(total -> {
                    if (total == 0) {
                        return Mono.just(new PageResponse<>(
                                List.of(), 0, 0L,
                                query.getPage() != null ? query.getPage() : 0,
                                query.getSize() != null ? query.getSize() : 10));
                    }
                    return groupCourseDao.searchGroupCourses(r2dbcEntityTemplate.getDatabaseClient(), query)
                            .map(groupCourseConverter::toDomain)
                            .collectList()
                            .map(courseList -> {
                                int size = query.getSize() != null ? query.getSize() : 10;
                                int totalPages = (int) Math.ceil((double) total / size);
                                return new PageResponse<>(
                                        courseList, totalPages, total,
                                        query.getPage() != null ? query.getPage() : 0, size);
                            });
                });
    }
}
