package de.cannamanage.service;

import de.cannamanage.domain.entity.*;
import de.cannamanage.domain.enums.*;
import de.cannamanage.service.repository.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.time.LocalDate;
import java.util.*;

/**
 * Service for club treasury management — fee schedules, payments, expenses, Kassenbuch.
 * All amounts in cents (Integer) to avoid floating-point precision issues.
 * LedgerEntry is append-only per §147 AO.
 */
@Service
@Transactional
public class FinanceService {

    private static final Logger log = LoggerFactory.getLogger(FinanceService.class);

    private final FeeScheduleRepository feeScheduleRepository;
    private final MemberFeeAssignmentRepository assignmentRepository;
    private final PaymentRepository paymentRepository;
    private final LedgerEntryRepository ledgerEntryRepository;
    private final AuditService auditService;

    public FinanceService(FeeScheduleRepository feeScheduleRepository,
                         MemberFeeAssignmentRepository assignmentRepository,
                         PaymentRepository paymentRepository,
                         LedgerEntryRepository ledgerEntryRepository,
                         AuditService auditService) {
        this.feeScheduleRepository = feeScheduleRepository;
        this.assignmentRepository = assignmentRepository;
        this.paymentRepository = paymentRepository;
        this.ledgerEntryRepository = ledgerEntryRepository;
        this.auditService = auditService;
    }

    // === Fee Schedule CRUD ===

    public FeeSchedule createFeeSchedule(UUID clubId, String name, int amountCents,
                                          FeeInterval interval, boolean isDefault) {
        var schedule = new FeeSchedule();
        schedule.setClubId(clubId);
        schedule.setName(name);
        schedule.setAmountCents(amountCents);
        schedule.setInterval(interval);
        schedule.setIsDefault(isDefault);
        schedule.setIsActive(true);

        // If this is set as default, unset any existing default
        if (isDefault) {
            feeScheduleRepository.findByClubIdAndIsDefaultTrue(clubId)
                    .ifPresent(existing -> {
                        existing.setIsDefault(false);
                        feeScheduleRepository.save(existing);
                    });
        }

        FeeSchedule saved = feeScheduleRepository.save(schedule);
        log.info("Fee schedule created: {} '{}' ({} cents/{}) for club {}",
                saved.getId(), name, amountCents, interval, clubId);

        auditService.log(AuditEventType.FEE_SCHEDULE_CREATED, "FeeSchedule",
                saved.getId().toString(), "Fee schedule created: " + name);

        return saved;
    }

    public FeeSchedule updateFeeSchedule(UUID scheduleId, String name, Integer amountCents,
                                          FeeInterval interval, Boolean isDefault) {
        FeeSchedule schedule = feeScheduleRepository.findById(scheduleId)
                .orElseThrow(() -> new NoSuchElementException("Fee schedule not found: " + scheduleId));

        if (name != null) schedule.setName(name);
        if (amountCents != null) schedule.setAmountCents(amountCents);
        if (interval != null) schedule.setInterval(interval);
        if (isDefault != null && isDefault) {
            feeScheduleRepository.findByClubIdAndIsDefaultTrue(schedule.getClubId())
                    .ifPresent(existing -> {
                        if (!existing.getId().equals(scheduleId)) {
                            existing.setIsDefault(false);
                            feeScheduleRepository.save(existing);
                        }
                    });
            schedule.setIsDefault(true);
        }

        FeeSchedule saved = feeScheduleRepository.save(schedule);
        auditService.log(AuditEventType.FEE_SCHEDULE_UPDATED, "FeeSchedule",
                saved.getId().toString(), "Fee schedule updated: " + saved.getName());

        return saved;
    }

    public void deactivateFeeSchedule(UUID scheduleId) {
        FeeSchedule schedule = feeScheduleRepository.findById(scheduleId)
                .orElseThrow(() -> new NoSuchElementException("Fee schedule not found: " + scheduleId));
        schedule.setIsActive(false);
        schedule.setIsDefault(false);
        feeScheduleRepository.save(schedule);
        log.info("Fee schedule deactivated: {}", scheduleId);
    }

    public List<FeeSchedule> getActiveFeeSchedules(UUID clubId) {
        return feeScheduleRepository.findByClubIdAndIsActiveTrue(clubId);
    }

    // === Fee Assignment ===

    public MemberFeeAssignment assignFeeSchedule(UUID memberId, UUID clubId,
                                                   UUID feeScheduleId, LocalDate validFrom) {
        // Close any existing open assignment
        assignmentRepository.findByMemberIdAndValidToIsNull(memberId)
                .ifPresent(existing -> {
                    existing.setValidTo(validFrom.minusDays(1));
                    assignmentRepository.save(existing);
                });

        var assignment = new MemberFeeAssignment();
        assignment.setMemberId(memberId);
        assignment.setClubId(clubId);
        assignment.setFeeScheduleId(feeScheduleId);
        assignment.setValidFrom(validFrom);

        return assignmentRepository.save(assignment);
    }

    // === Payments ===

    public Payment recordPayment(UUID clubId, UUID memberId, int amountCents,
                                  PaymentMethod paymentMethod, LocalDate periodFrom,
                                  LocalDate periodTo, String reference, String notes,
                                  UUID recordedBy) {
        var payment = new Payment();
        payment.setClubId(clubId);
        payment.setMemberId(memberId);
        payment.setAmountCents(amountCents);
        payment.setPaymentMethod(paymentMethod);
        payment.setStatus(PaymentStatus.PAID);
        payment.setPeriodFrom(periodFrom);
        payment.setPeriodTo(periodTo);
        payment.setReference(reference);
        payment.setNotes(notes);
        payment.setRecordedBy(recordedBy);
        payment.setPaidAt(Instant.now());

        Payment saved = paymentRepository.save(payment);

        // Create corresponding ledger entry (INCOME)
        var ledgerEntry = new LedgerEntry();
        ledgerEntry.setClubId(clubId);
        ledgerEntry.setTransactionType(TransactionType.INCOME);
        ledgerEntry.setCategory("MEMBERSHIP_FEE");
        ledgerEntry.setAmountCents(amountCents);
        ledgerEntry.setDescription("Mitgliedsbeitrag: " + periodFrom + " - " + periodTo);
        ledgerEntry.setReference(reference);
        ledgerEntry.setPaymentId(saved.getId());
        ledgerEntry.setRecordedBy(recordedBy);
        ledgerEntry.setTransactionDate(LocalDate.now());
        ledgerEntryRepository.save(ledgerEntry);

        auditService.log(AuditEventType.PAYMENT_RECORDED, "Payment",
                saved.getId().toString(),
                "Payment recorded: " + amountCents + " cents from member " + memberId);

        log.info("Payment recorded: {} cents from member {} for period {}-{} in club {}",
                amountCents, memberId, periodFrom, periodTo, clubId);

        return saved;
    }

    public Payment voidPayment(UUID paymentId, UUID voidedBy, String reason) {
        Payment payment = paymentRepository.findById(paymentId)
                .orElseThrow(() -> new NoSuchElementException("Payment not found: " + paymentId));

        if (payment.getStatus() == PaymentStatus.VOIDED) {
            throw new IllegalStateException("Payment already voided: " + paymentId);
        }

        payment.setStatus(PaymentStatus.VOIDED);
        payment.setVoidedAt(Instant.now());
        payment.setVoidedBy(voidedBy);
        payment.setVoidReason(reason);
        Payment saved = paymentRepository.save(payment);

        // Create compensating negative ledger entry
        var compensating = new LedgerEntry();
        compensating.setClubId(payment.getClubId());
        compensating.setTransactionType(TransactionType.EXPENSE);
        compensating.setCategory("MEMBERSHIP_FEE_VOID");
        compensating.setAmountCents(payment.getAmountCents());
        compensating.setDescription("Storno: Mitgliedsbeitrag " + payment.getPeriodFrom() + " - " + payment.getPeriodTo() + ". Grund: " + reason);
        compensating.setPaymentId(paymentId);
        compensating.setRecordedBy(voidedBy);
        compensating.setTransactionDate(LocalDate.now());
        ledgerEntryRepository.save(compensating);

        auditService.log(AuditEventType.PAYMENT_VOIDED, "Payment",
                paymentId.toString(), "Payment voided: " + reason);

        log.info("Payment voided: {} — reason: {}", paymentId, reason);

        return saved;
    }

    public Page<Payment> getPayments(UUID clubId, Pageable pageable) {
        return paymentRepository.findByClubId(clubId, pageable);
    }

    public Page<Payment> getPaymentsByMember(UUID clubId, UUID memberId, Pageable pageable) {
        return paymentRepository.findByClubIdAndMemberId(clubId, memberId, pageable);
    }

    public Page<Payment> getPaymentsByStatus(UUID clubId, PaymentStatus status, Pageable pageable) {
        return paymentRepository.findByClubIdAndStatus(clubId, status, pageable);
    }

    // === Expenses ===

    public LedgerEntry recordExpense(UUID clubId, ExpenseCategory category, int amountCents,
                                      String description, String reference, UUID recordedBy,
                                      LocalDate transactionDate) {
        var entry = new LedgerEntry();
        entry.setClubId(clubId);
        entry.setTransactionType(TransactionType.EXPENSE);
        entry.setCategory(category.name());
        entry.setAmountCents(amountCents);
        entry.setDescription(description);
        entry.setReference(reference);
        entry.setRecordedBy(recordedBy);
        entry.setTransactionDate(transactionDate);

        LedgerEntry saved = ledgerEntryRepository.save(entry);

        auditService.log(AuditEventType.EXPENSE_RECORDED, "LedgerEntry",
                saved.getId().toString(),
                "Expense recorded: " + amountCents + " cents (" + category + ")");

        log.info("Expense recorded: {} cents ({}) in club {}", amountCents, category, clubId);

        return saved;
    }

    // === Kassenbuch / Ledger ===

    public Page<LedgerEntry> getLedgerEntries(UUID clubId, LocalDate from, LocalDate to, Pageable pageable) {
        return ledgerEntryRepository.findByClubIdAndTransactionDateBetween(clubId, from, to, pageable);
    }

    // === Financial Summary ===

    public Map<String, Object> getFinancialSummary(UUID clubId, LocalDate from, LocalDate to) {
        Long totalIncome = ledgerEntryRepository.sumIncomeByClubAndDateRange(clubId, from, to);
        Long totalExpenses = ledgerEntryRepository.sumExpensesByClubAndDateRange(clubId, from, to);
        Long balance = ledgerEntryRepository.calculateBalance(clubId, to);

        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("totalIncomeCents", totalIncome);
        summary.put("totalExpensesCents", totalExpenses);
        summary.put("netCents", totalIncome - totalExpenses);
        summary.put("balanceCents", balance);
        summary.put("periodFrom", from.toString());
        summary.put("periodTo", to.toString());

        return summary;
    }

    // === Member Balance ===

    public Map<String, Object> getMemberBalance(UUID clubId, UUID memberId) {
        Long totalPaid = paymentRepository.sumPaidByMember(clubId, memberId);

        Map<String, Object> balance = new LinkedHashMap<>();
        balance.put("memberId", memberId);
        balance.put("totalPaidCents", totalPaid);

        // Get current fee assignment
        assignmentRepository.findByMemberIdAndValidToIsNull(memberId)
                .ifPresent(assignment -> {
                    feeScheduleRepository.findById(assignment.getFeeScheduleId())
                            .ifPresent(schedule -> {
                                balance.put("currentSchedule", schedule.getName());
                                balance.put("amountPerIntervalCents", schedule.getAmountCents());
                                balance.put("interval", schedule.getInterval().name());
                            });
                });

        return balance;
    }

    // === Outstanding / Overdue Members ===

    public List<Map<String, Object>> getOutstandingMembers(UUID clubId) {
        List<MemberFeeAssignment> activeAssignments = assignmentRepository.findByClubIdAndValidToIsNull(clubId);
        List<Map<String, Object>> outstanding = new ArrayList<>();

        for (MemberFeeAssignment assignment : activeAssignments) {
            Long totalPaid = paymentRepository.sumPaidByMember(clubId, assignment.getMemberId());
            // Simple check: if no payments at all, flag as outstanding
            if (totalPaid == 0) {
                Map<String, Object> entry = new LinkedHashMap<>();
                entry.put("memberId", assignment.getMemberId());
                entry.put("feeScheduleId", assignment.getFeeScheduleId());
                entry.put("validFrom", assignment.getValidFrom().toString());
                entry.put("totalPaidCents", 0);
                outstanding.add(entry);
            }
        }

        return outstanding;
    }
}
