package com.dne.ems.repository.impl;

import com.dne.ems.dto.HeatmapPointDTO;
import com.dne.ems.dto.PollutionStatsDTO;
import com.dne.ems.model.Feedback;
import com.dne.ems.model.enums.FeedbackStatus;
import com.dne.ems.repository.FeedbackRepository;
import com.dne.ems.service.JsonStorageService;
import com.fasterxml.jackson.core.type.TypeReference;
import jakarta.annotation.PostConstruct;
import org.springframework.context.annotation.Primary;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

@Repository
@Primary
public class JsonFeedbackRepositoryImpl implements FeedbackRepository {

    private static final String FILE_NAME = "feedbacks.json";
    private final JsonStorageService jsonStorageService;
    private final List<Feedback> cache = new ArrayList<>();
    private final AtomicLong idCounter = new AtomicLong(0);

    public JsonFeedbackRepositoryImpl(JsonStorageService jsonStorageService) {
        this.jsonStorageService = jsonStorageService;
    }

    @PostConstruct
    public void init() {
        List<Feedback> data = jsonStorageService.readData(FILE_NAME, new TypeReference<>() {});
        synchronized (cache) {
            cache.addAll(data);
            long maxId = data.stream()
                              .mapToLong(Feedback::getId)
                              .max()
                              .orElse(0L);
            idCounter.set(maxId);
        }
    }

    @Override
    public Feedback save(Feedback feedback) {
        synchronized (cache) {
            if (feedback.getId() == null) {
                feedback.setId(idCounter.incrementAndGet());
                cache.add(feedback);
            } else {
                cache.removeIf(f -> f.getId().equals(feedback.getId()));
                cache.add(feedback);
            }
            jsonStorageService.writeData(FILE_NAME, cache);
            return feedback;
        }
    }

    @Override
    public List<Feedback> saveAll(Iterable<Feedback> feedbacks) {
        synchronized (cache) {
            List<Feedback> result = new ArrayList<>();
            for (Feedback feedback : feedbacks) {
                result.add(save(feedback));
            }
            // Note: save() writes to file on every call. For bulk operations,
            // it would be more efficient to save all at once after the loop.
            // But for now, we stick to the existing pattern.
            return result;
        }
    }

    @Override
    public Optional<Feedback> findById(Long id) {
        synchronized (cache) {
            return cache.stream().filter(f -> f.getId().equals(id)).findFirst();
        }
    }

    @Override
    public List<Feedback> findAll() {
        synchronized (cache) {
            return new ArrayList<>(cache);
        }
    }

    @Override
    public void deleteAll() {
        synchronized (cache) {
            cache.clear();
            jsonStorageService.writeData(FILE_NAME, cache);
        }
    }

    @Override
    public long count() {
        synchronized (cache) {
            return cache.size();
        }
    }

    @Override
    public Optional<Feedback> findByEventId(String eventId) {
        synchronized (cache) {
            return cache.stream().filter(f -> f.getEventId().equals(eventId)).findFirst();
        }
    }

    @Override
    public List<Feedback> findByStatus(FeedbackStatus status) {
        synchronized (cache) {
            return cache.stream().filter(f -> f.getStatus() == status).collect(Collectors.toList());
        }
    }

    @Override
    public long countByStatus(FeedbackStatus status) {
        synchronized (cache) {
            return cache.stream().filter(f -> f.getStatus() == status).count();
        }
    }

    @Override
    public List<HeatmapPointDTO> getHeatmapData() {
        synchronized (cache) {
            return cache.stream()
                .filter(f -> f.getStatus() == FeedbackStatus.CONFIRMED && f.getGridX() != null && f.getGridY() != null)
                .collect(Collectors.groupingBy(
                    f -> new HeatmapPointDTO(f.getGridX(), f.getGridY(), 0L),
                    Collectors.counting()
                ))
                .entrySet().stream()
                .map(entry -> new HeatmapPointDTO(entry.getKey().gridX(), entry.getKey().gridY(), entry.getValue()))
                .collect(Collectors.toList());
        }
    }

    @Override
    public List<PollutionStatsDTO> countByPollutionType() {
        synchronized (cache) {
            return cache.stream()
                .collect(Collectors.groupingBy(Feedback::getPollutionType, Collectors.counting()))
                .entrySet().stream()
                .map(entry -> new PollutionStatsDTO(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
        }
    }

    @Override
    public long countBySubmitterId(Long submitterId) {
        synchronized (cache) {
            return cache.stream().filter(f -> Objects.equals(f.getSubmitterId(), submitterId)).count();
        }
    }

    @Override
    public long countByStatusAndSubmitterId(FeedbackStatus status, Long submitterId) {
        synchronized (cache) {
            return cache.stream()
                .filter(f -> f.getStatus() == status && Objects.equals(f.getSubmitterId(), submitterId))
                .count();
        }
    }

    @Override
    public List<Feedback> findBySubmitterId(Long submitterId) {
        synchronized (cache) {
            return cache.stream()
                .filter(f -> Objects.equals(f.getSubmitterId(), submitterId))
                .collect(Collectors.toList());
        }
    }

    @Override
    public Page<Feedback> findBySubmitterId(Long submitterId, Pageable pageable) {
        synchronized (cache) {
            List<Feedback> filteredFeedbacks = cache.stream()
                .filter(f -> Objects.equals(f.getSubmitterId(), submitterId))
                .collect(Collectors.toList());

            long total = filteredFeedbacks.size();
            
            int start = (int) pageable.getOffset();
            int end = Math.min((start + pageable.getPageSize()), filteredFeedbacks.size());

            List<Feedback> pageContent = (start > end) ? List.of() : filteredFeedbacks.subList(start, end);

            return new PageImpl<>(pageContent, pageable, total);
        }
    }
} 