package de.cannamanage.service;

import de.cannamanage.domain.constants.ComplianceConstants;
import de.cannamanage.domain.entity.Batch;
import de.cannamanage.domain.entity.Member;
import de.cannamanage.domain.entity.MonthlyQuota;
import de.cannamanage.domain.entity.Strain;
import de.cannamanage.domain.enums.BatchStatus;
import de.cannamanage.domain.enums.MemberStatus;
import de.cannamanage.service.dto.ComplianceCheckResult;
import de.cannamanage.service.dto.QuotaStatus;
import de.cannamanage.service.exception.BatchNotFoundException;
import de.cannamanage.service.exception.MemberNotFoundException;
import de.cannamanage.service.exception.QuotaExceededException;
import de.cannamanage.service.exception.QuotaViolationCode;
import de.cannamanage.service.repository.BatchRepository;
import de.cannamanage.service.repository.DistributionRepository;
import de.cannamanage.service.repository.MemberRepository;
import de.cannamanage.service.repository.MonthlyQuotaRepository;
import de.cannamanage.service.repository.StrainRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.UUID;

/**
 * Core compliance engine implementing CanG §19 distribution rules.
 * All checks are fail-fast — the first violation throws immediately.
 */
@Service
@Transactional
public class ComplianceService {

    private final MemberRepository memberRepository;
    private final DistributionRepository distributionRepository;
    private final BatchRepository batchRepository;
    private final MonthlyQuotaRepository monthlyQuotaRepository;
    private final StrainRepository strainRepository;

    public ComplianceService(
            MemberRepository memberRepository,
            DistributionRepository distributionRepository,
            BatchRepository batchRepository,
            MonthlyQuotaRepository monthlyQuotaRepository,
            StrainRepository strainRepository) {
        this.memberRepository = memberRepository;
        this.distributionRepository = distributionRepository;
        this.batchRepository = batchRepository;
        this.monthlyQuotaRepository = monthlyQuotaRepository;
        this.strainRepository = strainRepository;
    }

    /**
     * Checks whether a distribution is permitted under CanG §19.
     * Sequential fail-fast checks in legally mandated order.
     */
    public ComplianceCheckResult checkDistributionAllowed(
            UUID memberId, UUID batchId, BigDecimal quantityGrams) {

        // 1. Load member
        Member member = memberRepository.findById(memberId)
                .orElseThrow(() -> new MemberNotFoundException(memberId));

        // 2. Member must be ACTIVE
        if (member.getStatus() != MemberStatus.ACTIVE) {
            throw new QuotaExceededException(
                    QuotaViolationCode.MEMBER_INACTIVE,
                    "Member " + memberId + " is not active (status=" + member.getStatus() + ")");
        }

        // 3. Batch must be AVAILABLE
        Batch batch = batchRepository.findById(batchId)
                .orElseThrow(() -> new BatchNotFoundException(batchId));

        if (batch.getStatus() != BatchStatus.AVAILABLE) {
            throw new QuotaExceededException(
                    QuotaViolationCode.BATCH_UNAVAILABLE,
                    "Batch " + batchId + " is not available (status=" + batch.getStatus() + ")");
        }

        // 4. Load strain
        Strain strain = strainRepository.findById(batch.getStrainId())
                .orElseThrow(() -> new BatchNotFoundException(batch.getStrainId()));

        // 5. Under-21 THC restriction (CanG §19(4))
        if (member.isUnder21() &&
                strain.getThcPercentage().compareTo(ComplianceConstants.UNDER21_MAX_THC_PERCENTAGE) > 0) {
            throw new QuotaExceededException(
                    QuotaViolationCode.HIGH_THC_RESTRICTED_UNDER_21,
                    "Under-21 member cannot receive strain with THC% > " +
                    ComplianceConstants.UNDER21_MAX_THC_PERCENTAGE);
        }

        // 6. Daily limit check (CanG §19(2))
        LocalDate today = LocalDate.now(ZoneOffset.UTC);
        Instant dayStart = today.atStartOfDay(ZoneOffset.UTC).toInstant();
        Instant dayEnd = today.plusDays(1).atStartOfDay(ZoneOffset.UTC).toInstant();

        BigDecimal todayDistributed = distributionRepository
                .sumQuantityByMemberAndDay(memberId, dayStart, dayEnd);

        BigDecimal dailyLimit = ComplianceConstants.ADULT_DAILY_LIMIT_GRAMS;
        if (todayDistributed.add(quantityGrams).compareTo(dailyLimit) > 0) {
            throw new QuotaExceededException(
                    QuotaViolationCode.QUOTA_EXCEEDED_DAILY,
                    "Daily limit of " + dailyLimit + "g would be exceeded. " +
                    "Already distributed today: " + todayDistributed + "g");
        }

        // 7. Monthly limit check (CanG §19(2)/(3))
        int year = today.getYear();
        int month = today.getMonthValue();

        BigDecimal monthlyLimit = member.isUnder21()
                ? ComplianceConstants.UNDER21_MONTHLY_LIMIT_GRAMS
                : ComplianceConstants.ADULT_MONTHLY_LIMIT_GRAMS;

        MonthlyQuota quota = monthlyQuotaRepository
                .findByMemberIdAndYearAndMonth(memberId, year, month)
                .orElseGet(() -> createNewQuota(memberId, year, month, monthlyLimit));

        if (quota.getTotalDistributed().add(quantityGrams).compareTo(quota.getMaxAllowed()) > 0) {
            throw new QuotaExceededException(
                    QuotaViolationCode.QUOTA_EXCEEDED_MONTHLY,
                    "Monthly limit of " + quota.getMaxAllowed() + "g would be exceeded. " +
                    "Already distributed this month: " + quota.getTotalDistributed() + "g");
        }

        // 8. All checks passed
        BigDecimal remainingDaily = dailyLimit
                .subtract(todayDistributed)
                .subtract(quantityGrams);
        BigDecimal remainingMonthly = quota.getMaxAllowed()
                .subtract(quota.getTotalDistributed())
                .subtract(quantityGrams);

        return new ComplianceCheckResult(true, remainingDaily, remainingMonthly, member.isUnder21());
    }

    @Transactional(readOnly = true)
    public QuotaStatus getQuotaStatus(UUID memberId) {
        Member member = memberRepository.findById(memberId)
                .orElseThrow(() -> new MemberNotFoundException(memberId));

        LocalDate today = LocalDate.now(ZoneOffset.UTC);
        int year = today.getYear();
        int month = today.getMonthValue();

        BigDecimal monthlyLimit = member.isUnder21()
                ? ComplianceConstants.UNDER21_MONTHLY_LIMIT_GRAMS
                : ComplianceConstants.ADULT_MONTHLY_LIMIT_GRAMS;

        MonthlyQuota quota = monthlyQuotaRepository
                .findByMemberIdAndYearAndMonth(memberId, year, month)
                .orElseGet(() -> {
                    MonthlyQuota empty = new MonthlyQuota();
                    empty.setTotalDistributed(BigDecimal.ZERO);
                    empty.setMaxAllowed(monthlyLimit);
                    return empty;
                });

        BigDecimal remaining = quota.getMaxAllowed().subtract(quota.getTotalDistributed());

        return new QuotaStatus(
                quota.getMaxAllowed(),
                quota.getTotalDistributed(),
                remaining,
                member.isUnder21(),
                year,
                month
        );
    }

    public void validateMembershipAge(LocalDate dateOfBirth) {
        LocalDate today = LocalDate.now(ZoneOffset.UTC);
        int age = today.getYear() - dateOfBirth.getYear();
        if (today.getDayOfYear() < dateOfBirth.getDayOfYear()) {
            age--;
        }
        if (age < ComplianceConstants.MINIMUM_MEMBERSHIP_AGE) {
            throw new QuotaExceededException(
                    QuotaViolationCode.MEMBER_INACTIVE,
                    "Applicant is under minimum membership age of " +
                    ComplianceConstants.MINIMUM_MEMBERSHIP_AGE);
        }
    }

    public boolean isUnder21(LocalDate dateOfBirth) {
        LocalDate today = LocalDate.now(ZoneOffset.UTC);
        int age = today.getYear() - dateOfBirth.getYear();
        if (today.getDayOfYear() < dateOfBirth.getDayOfYear()) {
            age--;
        }
        return age < ComplianceConstants.UNDER21_THRESHOLD_AGE;
    }

    private MonthlyQuota createNewQuota(UUID memberId, int year, int month, BigDecimal maxAllowed) {
        MonthlyQuota quota = new MonthlyQuota();
        quota.setMemberId(memberId);
        quota.setYear(year);
        quota.setMonth(month);
        quota.setTotalDistributed(BigDecimal.ZERO);
        quota.setMaxAllowed(maxAllowed);
        return monthlyQuotaRepository.save(quota);
    }
}
