package com.honey.honey.controller;

import com.honey.honey.model.Payment;
import com.honey.honey.model.Payout;
import com.honey.honey.repository.PaymentRepository;
import com.honey.honey.repository.PayoutRepository;
import com.honey.honey.repository.UserARepository;
import com.honey.honey.repository.UserDRepository;
import com.honey.honey.service.AdminStatisticsTableService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/admin/analytics")
@RequiredArgsConstructor
@PreAuthorize("hasRole('ADMIN')")
public class AdminAnalyticsController {

    private static final BigDecimal ZERO = BigDecimal.ZERO;

    private final UserARepository userARepository;
    private final UserDRepository userDRepository;
    private final PaymentRepository paymentRepository;
    private final PayoutRepository payoutRepository;
    private final AdminStatisticsTableService adminStatisticsTableService;

    /**
     * Paginated daily / monthly statistics table (admin).
     */
    @GetMapping("/statistics-table")
    public ResponseEntity<Map<String, Object>> getStatisticsTable(
            @RequestParam String mode,
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "50") int size) {

        AdminStatisticsTableService.TableMode tableMode;
        try {
            tableMode = AdminStatisticsTableService.TableMode.valueOf(mode.trim().toUpperCase());
        } catch (IllegalArgumentException e) {
            return ResponseEntity.badRequest().build();
        }

        int cappedSize = Math.min(200, Math.max(1, size));
        int safePage = Math.max(0, page);

        var dtoPage = adminStatisticsTableService.getStatisticsTable(tableMode, safePage, cappedSize);

        Map<String, Object> response = new HashMap<>();
        response.put("content", dtoPage.getContent());
        response.put("totalElements", dtoPage.getTotalElements());
        response.put("totalPages", dtoPage.getTotalPages());
        response.put("currentPage", dtoPage.getNumber());
        response.put("size", dtoPage.getSize());
        response.put("hasNext", dtoPage.hasNext());
        response.put("hasPrevious", dtoPage.hasPrevious());
        response.put("mode", tableMode.name());

        return ResponseEntity.ok(response);
    }

    /**
     * Bounds for admin statistics-by-day UI: UTC day start of the earliest {@code db_users_d.date_reg}
     * and exclusive end of the latest 30-day window (start of tomorrow UTC).
     */
    @GetMapping("/meta")
    public ResponseEntity<Map<String, Object>> getAnalyticsMeta() {
        Instant latestWindowEndExclusive = Instant.now()
                .atZone(ZoneOffset.UTC)
                .toLocalDate()
                .plusDays(1)
                .atStartOfDay(ZoneOffset.UTC)
                .toInstant();

        long firstProjectDayStartEpoch = latestWindowEndExclusive.getEpochSecond() - 30L * 24 * 3600;
        var regOpt = userDRepository.findMinDateReg();
        if (regOpt.isPresent() && regOpt.get() != null && regOpt.get() > 0) {
            Instant reg = Instant.ofEpochSecond(regOpt.get());
            firstProjectDayStartEpoch = reg.atZone(ZoneOffset.UTC)
                    .toLocalDate()
                    .atStartOfDay(ZoneOffset.UTC)
                    .toEpochSecond();
        }

        Map<String, Object> response = new HashMap<>();
        response.put("latestWindowEndExclusiveEpoch", latestWindowEndExclusive.getEpochSecond());
        response.put("firstProjectDayStartEpoch", firstProjectDayStartEpoch);
        return ResponseEntity.ok(response);
    }

    /**
     * Revenue / deposits and payouts time series.
     * Optional {@code from} + {@code toExclusive} (epoch seconds, UTC instants): daily buckets in [from, toExclusive).
     * Otherwise {@code range}: 7d, 30d, 90d, 1y, all (legacy).
     */
    @GetMapping("/revenue")
    public ResponseEntity<Map<String, Object>> getRevenueAnalytics(
            @RequestParam(required = false) Long from,
            @RequestParam(required = false) Long toExclusive,
            @RequestParam(defaultValue = "30d") String range) {

        if (from != null && toExclusive != null) {
            Instant start = Instant.ofEpochSecond(from);
            Instant end = Instant.ofEpochSecond(toExclusive);
            if (!end.isAfter(start)) {
                return ResponseEntity.badRequest().build();
            }
            long maxSpanSeconds = 366L * 24 * 3600;
            if (end.getEpochSecond() - start.getEpochSecond() > maxSpanSeconds) {
                return ResponseEntity.badRequest().build();
            }
            List<Map<String, Object>> dataPoints = buildRevenueDailySeries(start, end);
            Map<String, Object> response = new HashMap<>();
            response.put("range", "custom");
            response.put("granularity", "daily");
            response.put("from", from);
            response.put("toExclusive", toExclusive);
            response.put("data", dataPoints);
            return ResponseEntity.ok(response);
        }

        Instant now = Instant.now();
        Instant startDate;
        String granularity;

        switch (range.toLowerCase()) {
            case "7d":
                startDate = now.minus(7, ChronoUnit.DAYS);
                granularity = "daily";
                break;
            case "30d":
                startDate = now.minus(30, ChronoUnit.DAYS);
                granularity = "daily";
                break;
            case "90d":
                startDate = now.minus(90, ChronoUnit.DAYS);
                granularity = "daily";
                break;
            case "1y":
                startDate = now.minus(365, ChronoUnit.DAYS);
                granularity = "weekly";
                break;
            case "all":
                startDate = Instant.ofEpochSecond(0);
                granularity = "monthly";
                break;
            default:
                startDate = now.minus(30, ChronoUnit.DAYS);
                granularity = "daily";
        }

        List<Map<String, Object>> dataPoints = new ArrayList<>();
        Instant current = startDate;

        while (current.isBefore(now)) {
            Instant periodEnd;
            if (granularity.equals("daily")) {
                periodEnd = current.plus(1, ChronoUnit.DAYS);
            } else if (granularity.equals("weekly")) {
                periodEnd = current.plus(7, ChronoUnit.DAYS);
            } else {
                periodEnd = current.plus(30, ChronoUnit.DAYS);
            }

            if (periodEnd.isAfter(now)) {
                periodEnd = now;
            }

            dataPoints.add(buildRevenuePoint(current, periodEnd));
            current = periodEnd;
        }

        Map<String, Object> response = new HashMap<>();
        response.put("range", range);
        response.put("granularity", granularity);
        response.put("data", dataPoints);

        return ResponseEntity.ok(response);
    }

    /**
     * User activity time series.
     * Optional {@code from} + {@code toExclusive} for daily buckets in [from, toExclusive).
     */
    @GetMapping("/activity")
    public ResponseEntity<Map<String, Object>> getActivityAnalytics(
            @RequestParam(required = false) Long from,
            @RequestParam(required = false) Long toExclusive,
            @RequestParam(defaultValue = "30d") String range) {

        if (from != null && toExclusive != null) {
            Instant start = Instant.ofEpochSecond(from);
            Instant end = Instant.ofEpochSecond(toExclusive);
            if (!end.isAfter(start)) {
                return ResponseEntity.badRequest().build();
            }
            long maxSpanSeconds = 366L * 24 * 3600;
            if (end.getEpochSecond() - start.getEpochSecond() > maxSpanSeconds) {
                return ResponseEntity.badRequest().build();
            }
            List<Map<String, Object>> dataPoints = buildActivityDailySeries(start, end);
            Map<String, Object> response = new HashMap<>();
            response.put("range", "custom");
            response.put("granularity", "daily");
            response.put("from", from);
            response.put("toExclusive", toExclusive);
            response.put("data", dataPoints);
            return ResponseEntity.ok(response);
        }

        Instant now = Instant.now();
        Instant startDate;
        String granularity;

        switch (range.toLowerCase()) {
            case "7d":
                startDate = now.minus(7, ChronoUnit.DAYS);
                granularity = "daily";
                break;
            case "30d":
                startDate = now.minus(30, ChronoUnit.DAYS);
                granularity = "daily";
                break;
            case "90d":
                startDate = now.minus(90, ChronoUnit.DAYS);
                granularity = "daily";
                break;
            case "1y":
                startDate = now.minus(365, ChronoUnit.DAYS);
                granularity = "weekly";
                break;
            case "all":
                startDate = Instant.ofEpochSecond(0);
                granularity = "monthly";
                break;
            default:
                startDate = now.minus(30, ChronoUnit.DAYS);
                granularity = "daily";
        }

        List<Map<String, Object>> dataPoints = new ArrayList<>();
        Instant current = startDate;

        while (current.isBefore(now)) {
            Instant periodEnd;
            if (granularity.equals("daily")) {
                periodEnd = current.plus(1, ChronoUnit.DAYS);
            } else if (granularity.equals("weekly")) {
                periodEnd = current.plus(7, ChronoUnit.DAYS);
            } else {
                periodEnd = current.plus(30, ChronoUnit.DAYS);
            }

            if (periodEnd.isAfter(now)) {
                periodEnd = now;
            }

            dataPoints.add(buildActivityPoint(current, periodEnd));
            current = periodEnd;
        }

        Map<String, Object> response = new HashMap<>();
        response.put("range", range);
        response.put("granularity", granularity);
        response.put("data", dataPoints);

        return ResponseEntity.ok(response);
    }

    private List<Map<String, Object>> buildActivityDailySeries(Instant startInclusive, Instant endExclusive) {
        List<Map<String, Object>> dataPoints = new ArrayList<>();
        Instant current = startInclusive;
        while (current.isBefore(endExclusive)) {
            Instant periodEnd = current.plus(1, ChronoUnit.DAYS);
            if (periodEnd.isAfter(endExclusive)) {
                periodEnd = endExclusive;
            }
            dataPoints.add(buildActivityPoint(current, periodEnd));
            current = periodEnd;
        }
        return dataPoints;
    }

    private Map<String, Object> buildActivityPoint(Instant current, Instant periodEnd) {
        int periodStartTs = (int) current.getEpochSecond();
        int periodEndTs = (int) periodEnd.getEpochSecond();
        long newUsers = userARepository.countByDateRegBetween(periodStartTs, periodEndTs);
        long activePlayers = userARepository.countByDateLoginBetween(periodStartTs, periodEndTs);
        Map<String, Object> point = new HashMap<>();
        point.put("date", current.getEpochSecond());
        point.put("newUsers", newUsers);
        point.put("activePlayers", activePlayers);
        point.put("rounds", 0L);
        return point;
    }

    private List<Map<String, Object>> buildRevenueDailySeries(Instant startInclusive, Instant endExclusive) {
        List<Map<String, Object>> dataPoints = new ArrayList<>();
        Instant current = startInclusive;
        while (current.isBefore(endExclusive)) {
            Instant periodEnd = current.plus(1, ChronoUnit.DAYS);
            if (periodEnd.isAfter(endExclusive)) {
                periodEnd = endExclusive;
            }
            dataPoints.add(buildRevenuePoint(current, periodEnd));
            current = periodEnd;
        }
        return dataPoints;
    }

    private Map<String, Object> buildRevenuePoint(Instant current, Instant periodEnd) {
        BigDecimal revenueUsd = paymentRepository
                .sumUsdAmountByStatusAndUsdAmountNotNullAndCreatedAtBetween(
                        Payment.PaymentStatus.COMPLETED, current, periodEnd)
                .orElse(ZERO);
        BigDecimal payoutsUsd = payoutRepository
                .sumUsdAmountByTypeCryptoAndStatusAndCreatedAtBetween(
                        Payout.PayoutStatus.COMPLETED, current, periodEnd)
                .orElse(ZERO);
        BigDecimal netRevenueUsd = revenueUsd.subtract(payoutsUsd);
        long firstTimeDeposits = paymentRepository.countByStatusAndFtdTrueAndUsdNotNullAndCompletedAtBetween(
                Payment.PaymentStatus.COMPLETED, current, periodEnd);

        Map<String, Object> point = new HashMap<>();
        point.put("date", current.getEpochSecond());
        point.put("revenue", revenueUsd);
        point.put("payouts", payoutsUsd);
        point.put("netRevenue", netRevenueUsd);
        point.put("firstTimeDeposits", firstTimeDeposits);
        return point;
    }
}
