package com.honey.honey.repository;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.Query;
import org.springframework.stereotype.Repository;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Date;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;

/**
 * Native aggregate queries for the admin statistics-by-day/month table.
 * Registration metrics use {@code db_users_d.date_reg} only (no join to {@code db_users_a}).
 */
@Repository
public class AdminStatisticsTableRepository {

    private static final BigDecimal ZERO = new BigDecimal("0.00");

    @PersistenceContext
    private EntityManager entityManager;

    /**
     * Mutable per-period aggregates filled by this repository.
     */
    public static final class StatisticsTableBucket {
        public long regs;
        public long regsOurs;
        public long depositsCount;
        public long ftdCount;
        public BigDecimal depositsSumUsd = ZERO;
        public BigDecimal avgPaymentUsd;
        public long withdrawalsCount;
        public BigDecimal withdrawalsSumUsd = ZERO;
    }

    public void loadRegistrationBucketsByDay(long startSec, long endSec, Map<LocalDate, StatisticsTableBucket> buckets) {
        String sql = """
                SELECT DATE(FROM_UNIXTIME(d.date_reg)) AS b, COUNT(*) AS c
                FROM db_users_d d
                WHERE d.date_reg >= :startSec AND d.date_reg < :endSec
                GROUP BY b
                """;
        Query q = entityManager.createNativeQuery(sql);
        q.setParameter("startSec", startSec);
        q.setParameter("endSec", endSec);
        List<Object[]> rows = q.getResultList();
        for (Object[] row : rows) {
            LocalDate d = toLocalDate(row[0]);
            if (d == null) {
                continue;
            }
            buckets.computeIfAbsent(d, x -> new StatisticsTableBucket()).regs = toLong(row[1]);
        }
    }

    public void loadRegistrationOursBucketsByDay(long startSec, long endSec, Map<LocalDate, StatisticsTableBucket> buckets) {
        String sql = """
                SELECT DATE(FROM_UNIXTIME(d.date_reg)) AS b, COUNT(*) AS c
                FROM db_users_d d
                WHERE d.date_reg >= :startSec AND d.date_reg < :endSec
                  AND d.referer_id_1 = d.master_id
                  AND d.id <> d.master_id
                  AND d.master_id > 0
                GROUP BY b
                """;
        Query q = entityManager.createNativeQuery(sql);
        q.setParameter("startSec", startSec);
        q.setParameter("endSec", endSec);
        @SuppressWarnings("unchecked")
        List<Object[]> rows = q.getResultList();
        for (Object[] row : rows) {
            LocalDate d = toLocalDate(row[0]);
            if (d == null) {
                continue;
            }
            buckets.computeIfAbsent(d, x -> new StatisticsTableBucket()).regsOurs = toLong(row[1]);
        }
    }

    public void loadPaymentBucketsByDay(Instant start, Instant end, Map<LocalDate, StatisticsTableBucket> buckets) {
        String sql = """
                SELECT DATE(p.completed_at) AS b,
                       COUNT(*) AS cnt,
                       COALESCE(SUM(p.usd_amount), 0) AS sum_usd,
                       AVG(p.usd_amount) AS avg_usd,
                       SUM(CASE WHEN p.ftd = 1 THEN 1 ELSE 0 END) AS ftd
                FROM payments p
                WHERE p.status = 'COMPLETED'
                  AND p.completed_at IS NOT NULL
                  AND p.completed_at >= :startTs AND p.completed_at < :endTs
                GROUP BY b
                """;
        Query q = entityManager.createNativeQuery(sql);
        q.setParameter("startTs", Timestamp.from(start));
        q.setParameter("endTs", Timestamp.from(end));
        @SuppressWarnings("unchecked")
        List<Object[]> rows = q.getResultList();
        for (Object[] row : rows) {
            LocalDate d = toLocalDate(row[0]);
            if (d == null) {
                continue;
            }
            StatisticsTableBucket b = buckets.computeIfAbsent(d, x -> new StatisticsTableBucket());
            b.depositsCount = toLong(row[1]);
            b.depositsSumUsd = readBd(row[2]);
            b.avgPaymentUsd = readBdNullable(row[3]);
            b.ftdCount = toLong(row[4]);
        }
    }

    public void loadPayoutBucketsByDay(Instant start, Instant end, Map<LocalDate, StatisticsTableBucket> buckets) {
        String sql = """
                SELECT DATE(COALESCE(p.resolved_at, p.updated_at, p.created_at)) AS b,
                       COUNT(*) AS cnt,
                       COALESCE(SUM(p.usd_amount), 0) AS sum_usd
                FROM payouts p
                WHERE p.status = 'COMPLETED'
                  AND COALESCE(p.resolved_at, p.updated_at, p.created_at) >= :startTs
                  AND COALESCE(p.resolved_at, p.updated_at, p.created_at) < :endTs
                GROUP BY b
                """;
        Query q = entityManager.createNativeQuery(sql);
        q.setParameter("startTs", Timestamp.from(start));
        q.setParameter("endTs", Timestamp.from(end));
        @SuppressWarnings("unchecked")
        List<Object[]> rows = q.getResultList();
        for (Object[] row : rows) {
            LocalDate d = toLocalDate(row[0]);
            if (d == null) {
                continue;
            }
            StatisticsTableBucket b = buckets.computeIfAbsent(d, x -> new StatisticsTableBucket());
            b.withdrawalsCount = toLong(row[1]);
            b.withdrawalsSumUsd = readBd(row[2]);
        }
    }

    public void loadRegistrationBucketsByMonth(long startSec, long endSec, Map<String, StatisticsTableBucket> buckets) {
        String sql = """
                SELECT DATE_FORMAT(FROM_UNIXTIME(d.date_reg), '%Y-%m') AS ym, COUNT(*) AS c
                FROM db_users_d d
                WHERE d.date_reg >= :startSec AND d.date_reg < :endSec
                GROUP BY ym
                """;
        Query q = entityManager.createNativeQuery(sql);
        q.setParameter("startSec", startSec);
        q.setParameter("endSec", endSec);
        @SuppressWarnings("unchecked")
        List<Object[]> rows = q.getResultList();
        for (Object[] row : rows) {
            String ym = row[0] != null ? row[0].toString() : null;
            if (ym == null) {
                continue;
            }
            buckets.computeIfAbsent(ym, x -> new StatisticsTableBucket()).regs = toLong(row[1]);
        }
    }

    public void loadRegistrationOursBucketsByMonth(long startSec, long endSec, Map<String, StatisticsTableBucket> buckets) {
        String sql = """
                SELECT DATE_FORMAT(FROM_UNIXTIME(d.date_reg), '%Y-%m') AS ym, COUNT(*) AS c
                FROM db_users_d d
                WHERE d.date_reg >= :startSec AND d.date_reg < :endSec
                  AND d.referer_id_1 = d.master_id
                  AND d.id <> d.master_id
                  AND d.master_id > 0
                GROUP BY ym
                """;
        Query q = entityManager.createNativeQuery(sql);
        q.setParameter("startSec", startSec);
        q.setParameter("endSec", endSec);
        @SuppressWarnings("unchecked")
        List<Object[]> rows = q.getResultList();
        for (Object[] row : rows) {
            String ym = row[0] != null ? row[0].toString() : null;
            if (ym == null) {
                continue;
            }
            buckets.computeIfAbsent(ym, x -> new StatisticsTableBucket()).regsOurs = toLong(row[1]);
        }
    }

    public void loadPaymentBucketsByMonth(Instant start, Instant end, Map<String, StatisticsTableBucket> buckets) {
        String sql = """
                SELECT DATE_FORMAT(p.completed_at, '%Y-%m') AS ym,
                       COUNT(*) AS cnt,
                       COALESCE(SUM(p.usd_amount), 0) AS sum_usd,
                       AVG(p.usd_amount) AS avg_usd,
                       SUM(CASE WHEN p.ftd = 1 THEN 1 ELSE 0 END) AS ftd
                FROM payments p
                WHERE p.status = 'COMPLETED'
                  AND p.completed_at IS NOT NULL
                  AND p.completed_at >= :startTs AND p.completed_at < :endTs
                GROUP BY ym
                """;
        Query q = entityManager.createNativeQuery(sql);
        q.setParameter("startTs", Timestamp.from(start));
        q.setParameter("endTs", Timestamp.from(end));
        @SuppressWarnings("unchecked")
        List<Object[]> rows = q.getResultList();
        for (Object[] row : rows) {
            String ym = row[0] != null ? row[0].toString() : null;
            if (ym == null) {
                continue;
            }
            StatisticsTableBucket b = buckets.computeIfAbsent(ym, x -> new StatisticsTableBucket());
            b.depositsCount = toLong(row[1]);
            b.depositsSumUsd = readBd(row[2]);
            b.avgPaymentUsd = readBdNullable(row[3]);
            b.ftdCount = toLong(row[4]);
        }
    }

    public void loadPayoutBucketsByMonth(Instant start, Instant end, Map<String, StatisticsTableBucket> buckets) {
        String sql = """
                SELECT DATE_FORMAT(COALESCE(p.resolved_at, p.updated_at, p.created_at), '%Y-%m') AS ym,
                       COUNT(*) AS cnt,
                       COALESCE(SUM(p.usd_amount), 0) AS sum_usd
                FROM payouts p
                WHERE p.status = 'COMPLETED'
                  AND COALESCE(p.resolved_at, p.updated_at, p.created_at) >= :startTs
                  AND COALESCE(p.resolved_at, p.updated_at, p.created_at) < :endTs
                GROUP BY ym
                """;
        Query q = entityManager.createNativeQuery(sql);
        q.setParameter("startTs", Timestamp.from(start));
        q.setParameter("endTs", Timestamp.from(end));
        @SuppressWarnings("unchecked")
        List<Object[]> rows = q.getResultList();
        for (Object[] row : rows) {
            String ym = row[0] != null ? row[0].toString() : null;
            if (ym == null) {
                continue;
            }
            StatisticsTableBucket b = buckets.computeIfAbsent(ym, x -> new StatisticsTableBucket());
            b.withdrawalsCount = toLong(row[1]);
            b.withdrawalsSumUsd = readBd(row[2]);
        }
    }

    private static LocalDate toLocalDate(Object o) {
        if (o == null) {
            return null;
        }
        if (o instanceof LocalDate ld) {
            return ld;
        }
        if (o instanceof Date sd) {
            return sd.toLocalDate();
        }
        if (o instanceof Timestamp ts) {
            return ts.toLocalDateTime().toLocalDate();
        }
        if (o instanceof java.util.Date ud) {
            return new Timestamp(ud.getTime()).toLocalDateTime().toLocalDate();
        }
        return null;
    }

    private static long toLong(Object o) {
        if (o == null) {
            return 0L;
        }
        if (o instanceof Number n) {
            return n.longValue();
        }
        return 0L;
    }

    private static BigDecimal readBd(Object o) {
        if (o == null) {
            return ZERO;
        }
        if (o instanceof BigDecimal bd) {
            return bd.setScale(2, RoundingMode.HALF_UP);
        }
        if (o instanceof Number n) {
            return BigDecimal.valueOf(n.doubleValue()).setScale(2, RoundingMode.HALF_UP);
        }
        return ZERO;
    }

    private static BigDecimal readBdNullable(Object o) {
        if (o == null) {
            return null;
        }
        if (o instanceof BigDecimal bd) {
            return bd.setScale(2, RoundingMode.HALF_UP);
        }
        if (o instanceof Number n) {
            return BigDecimal.valueOf(n.doubleValue()).setScale(2, RoundingMode.HALF_UP);
        }
        return null;
    }
}
