package pl.polskalokalnie.admin;

import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import org.springframework.stereotype.Service;
import pl.polskalokalnie.listing.ListingRepository;
import pl.polskalokalnie.listing.ListingStatus;
import pl.polskalokalnie.listing.PropertyListing;
import pl.polskalokalnie.message.Message;
import pl.polskalokalnie.message.MessageRepository;
import pl.polskalokalnie.report.ListingReport;
import pl.polskalokalnie.report.ListingReportRepository;
import pl.polskalokalnie.report.ListingReportStatus;
import pl.polskalokalnie.user.AccountType;
import pl.polskalokalnie.user.AppUser;
import pl.polskalokalnie.user.Role;
import pl.polskalokalnie.user.UserRepository;

@Service
public class AdminStatsService {

    private static final int MIN_PERIOD_DAYS = 1;
    private static final int MAX_PERIOD_DAYS = 60;
    private static final ZoneId ZONE_ID = ZoneId.of("Europe/Warsaw");

    private final ListingRepository listingRepository;
    private final UserRepository userRepository;
    private final ListingReportRepository listingReportRepository;
    private final MessageRepository messageRepository;

    public AdminStatsService(
            ListingRepository listingRepository,
            UserRepository userRepository,
            ListingReportRepository listingReportRepository,
            MessageRepository messageRepository
    ) {
        this.listingRepository = listingRepository;
        this.userRepository = userRepository;
        this.listingReportRepository = listingReportRepository;
        this.messageRepository = messageRepository;
    }

        public AdminStatsResponse getStats(int requestedPeriodDays) {
                int periodDays = Math.max(MIN_PERIOD_DAYS, Math.min(MAX_PERIOD_DAYS, requestedPeriodDays));
        Instant now = Instant.now();
        LocalDate today = LocalDate.now(ZONE_ID);
        LocalDate fromDate = today.minusDays(periodDays - 1L);
                return buildStats(fromDate, today, periodDays, now);
        }

        public AdminStatsResponse getStats(LocalDate requestedFromDate, LocalDate requestedToDate) {
                LocalDate today = LocalDate.now(ZONE_ID);
                LocalDate toDate = requestedToDate == null ? today : requestedToDate;
                LocalDate fromDate = requestedFromDate == null ? toDate : requestedFromDate;

                if (fromDate.isAfter(toDate)) {
                        LocalDate swapped = fromDate;
                        fromDate = toDate;
                        toDate = swapped;
                }

                if (toDate.isAfter(today)) {
                        toDate = today;
                }

                if (fromDate.isAfter(toDate)) {
                        fromDate = toDate;
                }

                long rawDays = java.time.temporal.ChronoUnit.DAYS.between(fromDate, toDate) + 1L;
                int periodDays = (int) Math.max(MIN_PERIOD_DAYS, Math.min(MAX_PERIOD_DAYS, rawDays));
                if (rawDays > MAX_PERIOD_DAYS) {
                        fromDate = toDate.minusDays(MAX_PERIOD_DAYS - 1L);
                }

                Instant now = Instant.now();
                return buildStats(fromDate, toDate, periodDays, now);
        }

        private AdminStatsResponse buildStats(LocalDate fromDate, LocalDate toDate, int periodDays, Instant generatedAt) {
        Instant periodStart = fromDate.atStartOfDay(ZONE_ID).toInstant();
                Instant periodEndExclusive = toDate.plusDays(1L).atStartOfDay(ZONE_ID).toInstant();
                Instant periodEndInclusive = generatedAt.isBefore(periodEndExclusive) ? generatedAt : periodEndExclusive.minusNanos(1L);

        List<PropertyListing> listings = listingRepository.findAll();
        List<AppUser> users = userRepository.findAll();
        List<ListingReport> reports = listingReportRepository.findAll();
        List<Message> messages = messageRepository.findAll();

        Long adminId = users.stream()
                .filter(user -> user.getRole() == Role.ADMIN)
                .map(AppUser::getId)
                .findFirst()
                .orElse(null);

        List<AppUser> regularUsers = users.stream()
                .filter(user -> user.getRole() != Role.ADMIN)
                .toList();

        List<Message> messagesToAdmin = adminId == null
                ? List.of()
                : messages.stream()
                        .filter(message -> message.getRecipientId().equals(adminId) && !message.getSenderId().equals(adminId))
                        .toList();

        AdminStatsResponse.Summary summary = new AdminStatsResponse.Summary(
                listings.stream().filter(item -> item.getStatus() == ListingStatus.APPROVED).count(),
                listings.stream().filter(item -> item.getStatus() == ListingStatus.PENDING).count(),
                regularUsers.size(),
                regularUsers.stream().filter(item -> !item.isVerified()).count(),
                reports.stream().filter(item -> item.getStatus() == ListingReportStatus.OPEN).count(),
                messagesToAdmin.size(),
                regularUsers.stream().filter(AppUser::isBlocked).count()
        );

        AdminStatsResponse.Period period = new AdminStatsResponse.Period(
                messagesToAdmin.stream().filter(item -> inRange(item.getCreatedAt(), periodStart, periodEndInclusive)).count(),
                listings.stream().filter(item -> inRange(item.getCreatedAt(), periodStart, periodEndInclusive)).count(),
                reports.stream().filter(item -> inRange(item.getCreatedAt(), periodStart, periodEndInclusive)).count(),
                regularUsers.stream()
                        .filter(item -> item.getAccountType() == AccountType.PERSONAL)
                        .filter(item -> inRange(item.getCreatedAt(), periodStart, periodEndInclusive))
                        .count(),
                regularUsers.stream()
                        .filter(item -> item.getAccountType() == AccountType.COMPANY)
                        .filter(item -> inRange(item.getCreatedAt(), periodStart, periodEndInclusive))
                        .count()
        );

        List<AdminStatsResponse.DailyPoint> daily = new ArrayList<>();
        for (LocalDate current = fromDate; !current.isAfter(toDate); current = current.plusDays(1)) {
            LocalDate nextDate = current.plusDays(1);
            Instant dayStart = current.atStartOfDay(ZONE_ID).toInstant();
            Instant dayEnd = nextDate.atStartOfDay(ZONE_ID).toInstant();

            daily.add(new AdminStatsResponse.DailyPoint(
                    current,
                    messagesToAdmin.stream().filter(item -> inRangeExclusiveEnd(item.getCreatedAt(), dayStart, dayEnd)).count(),
                    listings.stream().filter(item -> inRangeExclusiveEnd(item.getCreatedAt(), dayStart, dayEnd)).count(),
                    reports.stream().filter(item -> inRangeExclusiveEnd(item.getCreatedAt(), dayStart, dayEnd)).count(),
                    regularUsers.stream()
                            .filter(item -> item.getAccountType() == AccountType.PERSONAL)
                            .filter(item -> inRangeExclusiveEnd(item.getCreatedAt(), dayStart, dayEnd))
                            .count(),
                    regularUsers.stream()
                            .filter(item -> item.getAccountType() == AccountType.COMPANY)
                            .filter(item -> inRangeExclusiveEnd(item.getCreatedAt(), dayStart, dayEnd))
                            .count()
            ));
        }

        daily.sort(Comparator.comparing(AdminStatsResponse.DailyPoint::date));

                return new AdminStatsResponse(periodDays, fromDate, toDate, generatedAt, summary, period, daily);
    }

    private static boolean inRange(Instant timestamp, Instant startInclusive, Instant endInclusive) {
        return timestamp != null
                && !timestamp.isBefore(startInclusive)
                && !timestamp.isAfter(endInclusive);
    }

    private static boolean inRangeExclusiveEnd(Instant timestamp, Instant startInclusive, Instant endExclusive) {
        return timestamp != null
                && !timestamp.isBefore(startInclusive)
                && timestamp.isBefore(endExclusive);
    }
}
