package com.pressing.modules.accounting;

import com.pressing.common.exception.ApiException;
import com.pressing.domain.accounting.AccountingEntry;
import com.pressing.domain.accounting.AccountingEntryRepository;
import com.pressing.domain.accounting.AccountingSetting;
import com.pressing.domain.accounting.AccountingSettingRepository;
import com.pressing.modules.audit.AuditService;
import com.pressing.modules.common.DomainSupport;
import jakarta.validation.constraints.NotBlank;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountingService {

    private final AccountingEntryRepository entryRepository;
    private final AccountingSettingRepository settingRepository;
    private final DomainSupport domainSupport;
    private final AuditService auditService;

    public AccountingService(
            AccountingEntryRepository entryRepository,
            AccountingSettingRepository settingRepository,
            DomainSupport domainSupport,
            AuditService auditService
    ) {
        this.entryRepository = entryRepository;
        this.settingRepository = settingRepository;
        this.domainSupport = domainSupport;
        this.auditService = auditService;
    }

    public record EntryRequest(
            LocalDate entryDate,
            @NotBlank String journalCode,
            @NotBlank String accountCode,
            @NotBlank String label,
            BigDecimal debit,
            BigDecimal credit,
            String status,
            String notes
    ) {
    }

    public record EntryResponse(
            Long id,
            String entryNumber,
            LocalDate entryDate,
            String journalCode,
            String accountCode,
            String label,
            BigDecimal debit,
            BigDecimal credit,
            String status,
            String notes
    ) {
    }

    public record SettingRequest(@NotBlank String settingKey, @NotBlank String settingValue, String description) {
    }

    public record SettingResponse(Long id, String settingKey, String settingValue, String description) {
    }

    public record ReportRow(String journalCode, BigDecimal totalDebit, BigDecimal totalCredit) {
    }

    public List<EntryResponse> entries() {
        return entryRepository.findBySiteIdOrderByEntryDateDesc(domainSupport.siteId()).stream().map(this::toEntry).toList();
    }

    @Transactional
    public EntryResponse create(EntryRequest req) {
        AccountingEntry entity = new AccountingEntry();
        entity.setSiteId(domainSupport.siteId());
        entity.setEntryNumber(domainSupport.nextNumber("ECR", entryRepository.count() + 1));
        apply(entity, req);
        AccountingEntry saved = entryRepository.save(entity);
        auditService.log("ACCOUNTING_ENTRY", "Pièce " + saved.getEntryNumber(), "AccountingEntry", saved.getId());
        return toEntry(saved);
    }

    @Transactional
    public EntryResponse update(Long id, EntryRequest req) {
        AccountingEntry entity = entryRepository.findByIdAndSiteId(id, domainSupport.siteId())
                .orElseThrow(() -> ApiException.notFound("Pièce introuvable"));
        apply(entity, req);
        return toEntry(entryRepository.save(entity));
    }

    @Transactional
    public void delete(Long id) {
        AccountingEntry entity = entryRepository.findByIdAndSiteId(id, domainSupport.siteId())
                .orElseThrow(() -> ApiException.notFound("Pièce introuvable"));
        entryRepository.delete(entity);
    }

    public List<ReportRow> reports() {
        return entries().stream()
                .collect(java.util.stream.Collectors.groupingBy(EntryResponse::journalCode))
                .entrySet().stream()
                .map(e -> new ReportRow(
                        e.getKey(),
                        e.getValue().stream().map(EntryResponse::debit).reduce(BigDecimal.ZERO, BigDecimal::add),
                        e.getValue().stream().map(EntryResponse::credit).reduce(BigDecimal.ZERO, BigDecimal::add)
                )).toList();
    }

    public List<SettingResponse> settings() {
        return settingRepository.findBySiteIdOrderBySettingKeyAsc(domainSupport.siteId()).stream().map(this::toSetting).toList();
    }

    @Transactional
    public SettingResponse createSetting(SettingRequest req) {
        AccountingSetting entity = new AccountingSetting();
        entity.setSiteId(domainSupport.siteId());
        entity.setSettingKey(req.settingKey());
        entity.setSettingValue(req.settingValue());
        entity.setDescription(req.description());
        return toSetting(settingRepository.save(entity));
    }

    @Transactional
    public SettingResponse updateSetting(Long id, SettingRequest req) {
        AccountingSetting entity = settingRepository.findByIdAndSiteId(id, domainSupport.siteId())
                .orElseThrow(() -> ApiException.notFound("Paramètre introuvable"));
        entity.setSettingKey(req.settingKey());
        entity.setSettingValue(req.settingValue());
        entity.setDescription(req.description());
        return toSetting(settingRepository.save(entity));
    }

    private void apply(AccountingEntry entity, EntryRequest req) {
        entity.setEntryDate(req.entryDate() == null ? LocalDate.now() : req.entryDate());
        entity.setJournalCode(req.journalCode());
        entity.setAccountCode(req.accountCode());
        entity.setLabel(req.label());
        entity.setDebit(req.debit() == null ? BigDecimal.ZERO : req.debit());
        entity.setCredit(req.credit() == null ? BigDecimal.ZERO : req.credit());
        entity.setStatus(req.status() == null ? "DRAFT" : req.status());
        entity.setNotes(req.notes());
    }

    private EntryResponse toEntry(AccountingEntry e) {
        return new EntryResponse(e.getId(), e.getEntryNumber(), e.getEntryDate(), e.getJournalCode(), e.getAccountCode(),
                e.getLabel(), e.getDebit(), e.getCredit(), e.getStatus(), e.getNotes());
    }

    private SettingResponse toSetting(AccountingSetting s) {
        return new SettingResponse(s.getId(), s.getSettingKey(), s.getSettingValue(), s.getDescription());
    }
}
