package com.pressing.modules.invoice;

import com.pressing.common.dto.PageResponse;
import com.pressing.common.exception.ApiException;
import com.pressing.domain.catalog.Article;
import com.pressing.domain.catalog.ArticleColor;
import com.pressing.domain.catalog.ArticleColorRepository;
import com.pressing.domain.catalog.ArticleRepository;
import com.pressing.domain.catalog.ArticleType;
import com.pressing.domain.catalog.ArticleTypeRepository;
import com.pressing.domain.catalog.PricingRepository;
import com.pressing.domain.catalog.ServiceOffering;
import com.pressing.domain.catalog.ServiceOfferingRepository;
import com.pressing.domain.customer.Customer;
import com.pressing.domain.invoice.Invoice;
import com.pressing.domain.invoice.InvoiceLine;
import com.pressing.domain.invoice.InvoiceRepository;
import com.pressing.domain.invoice.InvoiceStatus;
import com.pressing.domain.invoice.PaymentStatus;
import com.pressing.modules.audit.AuditService;
import com.pressing.modules.common.DomainSupport;
import com.pressing.modules.customer.CustomerService;
import com.pressing.modules.customer.dto.CustomerDtos.CustomerRequest;
import com.pressing.modules.invoice.dto.InvoiceDtos.InvoiceLineRequest;
import com.pressing.modules.invoice.dto.InvoiceDtos.InvoiceLineResponse;
import com.pressing.modules.invoice.dto.InvoiceDtos.InvoiceRequest;
import com.pressing.modules.invoice.dto.InvoiceDtos.InvoiceResponse;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Predicate;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
public class InvoiceService {

    private final InvoiceRepository invoiceRepository;
    private final ServiceOfferingRepository serviceRepository;
    private final ArticleTypeRepository articleTypeRepository;
    private final ArticleRepository articleRepository;
    private final ArticleColorRepository colorRepository;
    private final PricingRepository pricingRepository;
    private final CustomerService customerService;
    private final DomainSupport domainSupport;
    private final AuditService auditService;

    public InvoiceService(
            InvoiceRepository invoiceRepository,
            ServiceOfferingRepository serviceRepository,
            ArticleTypeRepository articleTypeRepository,
            ArticleRepository articleRepository,
            ArticleColorRepository colorRepository,
            PricingRepository pricingRepository,
            CustomerService customerService,
            DomainSupport domainSupport,
            AuditService auditService
    ) {
        this.invoiceRepository = invoiceRepository;
        this.serviceRepository = serviceRepository;
        this.articleTypeRepository = articleTypeRepository;
        this.articleRepository = articleRepository;
        this.colorRepository = colorRepository;
        this.pricingRepository = pricingRepository;
        this.customerService = customerService;
        this.domainSupport = domainSupport;
        this.auditService = auditService;
    }

    public PageResponse<InvoiceResponse> search(String q, LocalDate from, LocalDate to, String status, int page, int size) {
        Long siteId = domainSupport.siteId();
        Specification<Invoice> spec = (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("siteId"), siteId));
            if (status != null && !status.isBlank()) {
                predicates.add(cb.equal(root.get("status"), InvoiceStatus.valueOf(status)));
            }
            if (from != null) {
                predicates.add(cb.greaterThanOrEqualTo(root.get("depositDateTime"), from.atStartOfDay()));
            }
            if (to != null) {
                predicates.add(cb.lessThan(root.get("depositDateTime"), to.plusDays(1).atStartOfDay()));
            }
            if (q != null && !q.isBlank()) {
                String like = "%" + q.toLowerCase() + "%";
                Join<Object, Object> customer = root.join("customer", JoinType.LEFT);
                predicates.add(cb.or(
                        cb.like(cb.lower(root.get("invoiceNumber")), like),
                        cb.like(cb.lower(customer.get("firstName")), like),
                        cb.like(cb.lower(customer.get("lastName")), like),
                        cb.like(cb.lower(customer.get("phone")), like)
                ));
            }
            return cb.and(predicates.toArray(Predicate[]::new));
        };
        return PageResponse.from(invoiceRepository.findAll(spec, PageRequest.of(page, size, Sort.by("depositDateTime").descending()))
                .map(this::toDto));
    }

    public InvoiceResponse get(Long id) {
        return toDto(find(id));
    }

  @Transactional(readOnly = true)
public InvoiceResponse getByNumber(String number) {
    Invoice invoice = invoiceRepository.findByInvoiceNumberAndSiteIdFetchLines(number, domainSupport.siteId())
            .orElseThrow(() -> ApiException.notFound("Facture introuvable"));
    return toDto(invoice);
}

@Transactional(readOnly = true)
public Invoice findByNumber(String number) {
    return invoiceRepository.findByInvoiceNumberAndSiteIdFetchLines(number, domainSupport.siteId())
            .orElseThrow(() -> ApiException.notFound("Facture introuvable"));
}


    public List<Invoice> searchInvoices(String q) {
        if (q == null || q.isBlank()) {
            return List.of();
        }
        return invoiceRepository.searchForPayment(domainSupport.siteId(), q);
    }

    @Transactional
    public InvoiceResponse create(InvoiceRequest req) {
        Invoice invoice = new Invoice();
        invoice.setSiteId(domainSupport.siteId());
        invoice.setInvoiceNumber(domainSupport.nextNumber("FAC", invoiceRepository.count() + 1));
        apply(invoice, req);
        if (req.validate()) {
            invoice.setStatus(InvoiceStatus.VALIDATED);
        } else {
            invoice.setStatus(InvoiceStatus.DRAFT);
        }
        Invoice saved = invoiceRepository.save(invoice);
        auditService.log(req.validate() ? "VALIDATE_INVOICE" : "DRAFT_INVOICE",
                "Facture " + saved.getInvoiceNumber(), "Invoice", saved.getId());
        return toDto(saved);
    }

    @Transactional
    public InvoiceResponse update(Long id, InvoiceRequest req) {
        Invoice invoice = find(id);
        if (invoice.getStatus() == InvoiceStatus.PICKED_UP) {
            throw ApiException.badRequest("Impossible de modifier une facture déjà retirée");
        }
        apply(invoice, req);
        return toDto(invoiceRepository.save(invoice));
    }

    @Transactional
    public InvoiceResponse validate(Long id) {
        Invoice invoice = find(id);
        invoice.setStatus(InvoiceStatus.VALIDATED);
        Invoice saved = invoiceRepository.save(invoice);
        auditService.log("VALIDATE_INVOICE", "Validation facture " + saved.getInvoiceNumber(), "Invoice", saved.getId());
        return toDto(saved);
    }

    @Transactional
    public void delete(Long id) {
        Invoice invoice = find(id);
        if (invoice.getStatus() != InvoiceStatus.DRAFT) {
            throw ApiException.badRequest("Seuls les brouillons peuvent être supprimés");
        }
        invoiceRepository.delete(invoice);
    }

    public Invoice find(Long id) {
        return invoiceRepository.findByIdAndSiteIdFetchLines(id, domainSupport.siteId())
                .orElseThrow(() -> ApiException.notFound("Facture introuvable"));
    }

    public void refreshPayment(Invoice invoice) {
        if (invoice.getAmountPaid().compareTo(BigDecimal.ZERO) <= 0) {
            invoice.setPaymentStatus(PaymentStatus.UNPAID);
        } else if (invoice.getAmountPaid().compareTo(invoice.getAmountTtc()) >= 0) {
            invoice.setPaymentStatus(PaymentStatus.PAID);
            invoice.setAmountDue(BigDecimal.ZERO);
        } else {
            invoice.setPaymentStatus(PaymentStatus.DEPOSIT);
        }
        invoice.setAmountDue(invoice.getAmountTtc().subtract(invoice.getAmountPaid()).max(BigDecimal.ZERO));
    }

    private void apply(Invoice invoice, InvoiceRequest req) {
        CustomerRequest customerReq = new CustomerRequest(req.firstName(), req.lastName(), req.phone(),
                req.phoneSecondary(), req.email(), null, false, null);
        Customer customer = customerService.findOrCreate(customerReq, req.customerId());
        if (customer.isBlacklisted()) {
            throw ApiException.badRequest("Ce client est sur liste noire");
        }
        ServiceOffering service = serviceRepository.findByIdAndSiteId(req.serviceId(), domainSupport.siteId())
                .orElseThrow(() -> ApiException.notFound("Type de lavage introuvable"));
        invoice.setCustomer(customer);
        invoice.setService(service);
        invoice.setDepositDateTime(req.depositDateTime() == null ? LocalDateTime.now() : req.depositDateTime());
        invoice.setExpectedPickupDate(req.expectedPickupDate());
        invoice.setNotes(req.notes());
        invoice.setDiscountPercent(nvl(req.discountPercent()));

        // Remplacement de domainSupport.currentSite().getDefaultTaxRate() 
    // Utilise req.taxRate() ou zéro/valeur par défaut du système si non renseigné
    invoice.setTaxRate(req.taxRate() != null ? req.taxRate() : BigDecimal.ZERO);
    

        invoice.getLines().clear();
        List<InvoiceLineRequest> lines = req.lines() == null ? List.of() : req.lines();
        if (lines.isEmpty()) {
            InvoiceLine line = new InvoiceLine();
            line.setInvoice(invoice);
            line.setDesignation(service.getLabel());
            line.setQuantity(req.articleCount() == null ? 1 : req.articleCount());
            line.setUnitPrice(defaultPrice(service.getId(), null));
            line.setLineTotal(line.getUnitPrice().multiply(BigDecimal.valueOf(line.getQuantity())));
            invoice.getLines().add(line);
        } else {
            for (InvoiceLineRequest lineReq : lines) {

                InvoiceLine line = new InvoiceLine();
                line.setInvoice(invoice);
                line.setDesignation(lineReq.designation());
                line.setBrand(lineReq.brand());             
                line.setObservation(lineReq.observation());  
                line.setQuantity(lineReq.quantity());
                if (lineReq.articleTypeId() != null) {
                    ArticleType type = articleTypeRepository.findByIdAndSiteId(lineReq.articleTypeId(), domainSupport.siteId())
                            .orElseThrow(() -> ApiException.notFound("Type d'article introuvable"));
                    line.setArticleType(type);
                }
                if (lineReq.articleId() != null) {
                    Article article = articleRepository.findByIdAndSiteId(lineReq.articleId(), domainSupport.siteId())
                            .orElseThrow(() -> ApiException.notFound("Article introuvable"));
                    line.setArticle(article);
                }
                if (lineReq.colorId() != null) {
                    ArticleColor color = colorRepository.findByIdAndSiteId(lineReq.colorId(), domainSupport.siteId())
                            .orElseThrow(() -> ApiException.notFound("Couleur introuvable"));
                    line.setColor(color);
                }
                BigDecimal unit = lineReq.unitPrice() != null
                        ? lineReq.unitPrice()
                        : defaultPrice(service.getId(), lineReq.articleTypeId());
                line.setUnitPrice(unit);
                line.setLineTotal(unit.multiply(BigDecimal.valueOf(lineReq.quantity())));
                invoice.getLines().add(line);
            }
        }
        recalculate(invoice);
        if (req.articleCount() != null) {
            invoice.setArticleCount(req.articleCount());
        }
    }

    private void recalculate(Invoice invoice) {
        BigDecimal ht = invoice.getLines().stream()
                .map(InvoiceLine::getLineTotal)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        invoice.setAmountHt(ht);
        invoice.setArticleCount(invoice.getLines().stream().mapToInt(InvoiceLine::getQuantity).sum());
        BigDecimal discount = ht.multiply(invoice.getDiscountPercent()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
        invoice.setDiscountAmount(discount);
        BigDecimal after = ht.subtract(discount);
        invoice.setAmountAfterDiscount(after);
        BigDecimal tax = after.multiply(invoice.getTaxRate()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
        invoice.setTaxAmount(tax);
        invoice.setAmountTtc(after.add(tax));
        if (invoice.getAmountPaid() == null) {
            invoice.setAmountPaid(BigDecimal.ZERO);
        }
        refreshPayment(invoice);
    }

    private BigDecimal defaultPrice(Long serviceId, Long articleTypeId) {
        if (articleTypeId != null) {
            return pricingRepository.findBySiteIdAndServiceIdAndArticleTypeId(domainSupport.siteId(), serviceId, articleTypeId)
                    .map(p -> p.getUnitPrice())
                    .orElse(BigDecimal.ZERO);
        }
        return pricingRepository.findBySiteIdOrderByIdDesc(domainSupport.siteId()).stream()
                .filter(p -> p.getService().getId().equals(serviceId))
                .findFirst()
                .map(p -> p.getUnitPrice())
                .orElse(BigDecimal.ZERO);
    }

    private InvoiceResponse toDto(Invoice invoice) {
        

        // Dans la boucle de conversion des lignes :
List<InvoiceLineResponse> lines = invoice.getLines().stream().map(line -> new InvoiceLineResponse(
        line.getId(),
        line.getArticleType() == null ? null : line.getArticleType().getId(),
        line.getArticle() == null ? null : line.getArticle().getId(),
        line.getColor() == null ? null : line.getColor().getId(),
        line.getBrand(),                      // <--- AJOUT
        line.getObservation(),                // <--- AJOUT
        line.getDesignation(),
        line.getQuantity(),
        line.getUnitPrice(),
        line.getLineTotal(),
        line.getQuantityPickedUp(),
        line.getStorageLocation() == null ? null : line.getStorageLocation().getId(),
        line.getStorageLocation() == null ? null : line.getStorageLocation().getLabel()
)).toList();

        Customer c = invoice.getCustomer();
        return new InvoiceResponse(
                invoice.getId(),
                invoice.getInvoiceNumber(),
                c.getId(),
                c.getFirstName() + " " + c.getLastName(),
                c.getPhone(),
                invoice.getService().getId(),
                invoice.getService().getLabel(),
                invoice.getDepositDateTime(),
                invoice.getExpectedPickupDate(),
                invoice.getArticleCount(),
                invoice.getAmountHt(),
                invoice.getDiscountPercent(),
                invoice.getDiscountAmount(),
                invoice.getAmountAfterDiscount(),
                invoice.getTaxRate(),
                invoice.getTaxAmount(),
                invoice.getAmountTtc(),
                invoice.getAmountPaid(),
                invoice.getAmountDue(),
                invoice.getStatus().name(),
                invoice.getPaymentStatus().name(),
                invoice.getNotes(),
                invoice.getCreatedBy(),
                lines
        );
    }

    private BigDecimal nvl(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
}
