package de.cannamanage.service;

import de.cannamanage.domain.constants.ComplianceConstants;
import de.cannamanage.domain.entity.Club;
import de.cannamanage.domain.entity.Distribution;
import de.cannamanage.domain.entity.Member;
import de.cannamanage.domain.entity.StaffAccount;
import de.cannamanage.service.exception.PreventionOfficerLimitExceededException;
import de.cannamanage.service.repository.ClubRepository;
import de.cannamanage.service.repository.DistributionRepository;
import de.cannamanage.service.repository.MemberRepository;
import de.cannamanage.service.repository.StaffAccountRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.UUID;

/**
 * Service for prevention officer assignment and under-21 member monitoring.
 * Enforces the configurable limit per club and provides prevention-relevant data.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PreventionOfficerService {

    private final StaffAccountRepository staffAccountRepository;
    private final ClubRepository clubRepository;
    private final MemberRepository memberRepository;
    private final DistributionRepository distributionRepository;

    /**
     * Assign or revoke prevention officer status on a staff account.
     * On assign: enforces club.maxPreventionOfficers limit.
     * On revoke: always succeeds.
     */
    @Transactional
    public StaffAccount setPreventionOfficer(UUID tenantId, UUID staffId, boolean assign) {
        StaffAccount staff = staffAccountRepository.findById(staffId)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Staff account not found"));

        if (!staff.getTenantId().equals(tenantId)) {
            throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Staff account not found");
        }

        if (assign) {
            // Check limit before assigning
            long currentCount = staffAccountRepository.countByTenantIdAndPreventionOfficerTrueAndActiveTrue(tenantId);
            Club club = clubRepository.findById(tenantId)
                    .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Club not found"));

            if (currentCount >= club.getMaxPreventionOfficers()) {
                throw new PreventionOfficerLimitExceededException(club.getMaxPreventionOfficers());
            }

            staff.setPreventionOfficer(true);
            log.info("Prevention officer assigned: staffId={}, tenantId={}", staffId, tenantId);
        } else {
            staff.setPreventionOfficer(false);
            log.info("Prevention officer revoked: staffId={}, tenantId={}", staffId, tenantId);
        }

        return staffAccountRepository.save(staff);
    }

    /**
     * Returns all under-21 members for the tenant with their current month distribution data.
     */
    @Transactional(readOnly = true)
    public List<Member> getUnder21Members(UUID tenantId) {
        return memberRepository.findByTenantIdAndUnder21True(tenantId);
    }

    /**
     * Returns the number of distributions for a member in the current month.
     */
    @Transactional(readOnly = true)
    public long countCurrentMonthDistributions(UUID tenantId, UUID memberId) {
        Instant monthStart = getMonthStart();
        Instant now = Instant.now();
        List<Distribution> distributions = distributionRepository
                .findByTenantIdAndDistributedAtBetween(tenantId, monthStart, now);
        return distributions.stream()
                .filter(d -> d.getMemberId().equals(memberId))
                .count();
    }

    /**
     * Returns the total grams distributed to a member in the current month.
     */
    @Transactional(readOnly = true)
    public BigDecimal sumCurrentMonthGrams(UUID tenantId, UUID memberId) {
        Instant monthStart = getMonthStart();
        Instant now = Instant.now();
        List<Distribution> distributions = distributionRepository
                .findByTenantIdAndDistributedAtBetween(tenantId, monthStart, now);
        return distributions.stream()
                .filter(d -> d.getMemberId().equals(memberId))
                .map(Distribution::getQuantityGrams)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /**
     * Calculates the monthly limit for a member based on their age.
     */
    public BigDecimal getMonthlyLimit(Member member) {
        if (member.isUnder21()) {
            return ComplianceConstants.UNDER21_MONTHLY_LIMIT_GRAMS;
        }
        return ComplianceConstants.ADULT_MONTHLY_LIMIT_GRAMS;
    }

    /**
     * Calculates the age of a member.
     */
    public int calculateAge(LocalDate dateOfBirth) {
        return Period.between(dateOfBirth, LocalDate.now()).getYears();
    }

    private Instant getMonthStart() {
        ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Europe/Berlin"));
        ZonedDateTime monthStart = now.withDayOfMonth(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
        return monthStart.toInstant();
    }
}
