package pl.polskalokalnie.send;

import java.time.Instant;
import java.time.LocalDate;
import java.util.List;
import org.springframework.data.domain.Limit;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import pl.polskalokalnie.campaign.Campaign;
import pl.polskalokalnie.campaign.CampaignRecipient;
import pl.polskalokalnie.campaign.CampaignRecipientRepository;
import pl.polskalokalnie.campaign.CampaignRepository;
import pl.polskalokalnie.campaign.CampaignStatus;
import pl.polskalokalnie.campaign.Channel;
import pl.polskalokalnie.campaign.RecipientStatus;
import pl.polskalokalnie.mailconfig.MailConfigService;
import pl.polskalokalnie.mailconfig.SmsGatewayConfig;
import pl.polskalokalnie.mailconfig.SmtpConfig;

/**
 * Worker wysylajacy kampanie z outboxu. Cyklicznie promuje zaplanowane kampanie, ktorych termin
 * nadszedl, a nastepnie wysyla zakolejkowane wiadomosci respektujac dzienny limit. Wysylka
 * odbywa sie tylko gdy odpowiednia konfiguracja (SMTP/SMS) jest wlaczona - w przeciwnym razie
 * pozycje pozostaja w kolejce (tryb sandbox). Jeden przebieg przetwarza ograniczona partie, by
 * nie blokowac watku schedulera.
 */
@Component
public class CampaignDispatcher {

    private static final int BATCH_PER_TICK = 25;

    private final CampaignRepository campaignRepository;
    private final CampaignRecipientRepository recipientRepository;
    private final MailConfigService mailConfigService;
    private final EmailSender emailSender;
    private final SmsSender smsSender;
    private final MessageComposer messageComposer;

    public CampaignDispatcher(CampaignRepository campaignRepository,
            CampaignRecipientRepository recipientRepository,
            MailConfigService mailConfigService,
            EmailSender emailSender,
            SmsSender smsSender,
            MessageComposer messageComposer) {
        this.campaignRepository = campaignRepository;
        this.recipientRepository = recipientRepository;
        this.mailConfigService = mailConfigService;
        this.emailSender = emailSender;
        this.smsSender = smsSender;
        this.messageComposer = messageComposer;
    }

    @Scheduled(fixedDelayString = "${app.campaign.dispatch-interval-ms:15000}", initialDelay = 20000)
    public void dispatch() {
        promoteScheduled();
        for (Campaign campaign : campaignRepository.findByStatus(CampaignStatus.SENDING)) {
            processCampaign(campaign);
        }
    }

    private void promoteScheduled() {
        Instant now = Instant.now();
        for (Campaign campaign : campaignRepository.findByStatus(CampaignStatus.SCHEDULED)) {
            if (campaign.getScheduledAt() == null || !campaign.getScheduledAt().isAfter(now)) {
                campaign.setStatus(CampaignStatus.SENDING);
                campaignRepository.save(campaign);
            }
        }
    }

    private void processCampaign(Campaign campaign) {
        // Reset dziennego licznika przy zmianie daty.
        LocalDate today = LocalDate.now();
        if (!today.equals(campaign.getSentTodayDate())) {
            campaign.setSentToday(0);
            campaign.setSentTodayDate(today);
            campaignRepository.save(campaign);
        }

        int dailyLimit = campaign.getDailyLimit() != null ? campaign.getDailyLimit() : Integer.MAX_VALUE;
        int remainingToday = dailyLimit - (campaign.getSentToday() != null ? campaign.getSentToday() : 0);
        if (remainingToday <= 0) {
            return;
        }

        // Konfiguracja wlaczona? Jesli nie - tryb sandbox, nic nie wychodzi (pozycje zostaja w kolejce).
        SmtpConfig smtp = campaign.getChannel() == Channel.EMAIL ? mailConfigService.getSmtp() : null;
        SmsGatewayConfig sms = campaign.getChannel() == Channel.SMS ? mailConfigService.getSms() : null;
        if (campaign.getChannel() == Channel.EMAIL && (smtp == null || !smtp.isEnabled())) {
            return;
        }
        if (campaign.getChannel() == Channel.SMS && (sms == null || !sms.isEnabled())) {
            return;
        }

        int batch = Math.min(BATCH_PER_TICK, remainingToday);
        List<CampaignRecipient> queued = recipientRepository
                .findByCampaignIdAndStatusOrderByIdAsc(campaign.getId(), RecipientStatus.QUEUED, Limit.of(batch));

        int sentNow = 0;
        for (CampaignRecipient recipient : queued) {
            SendResult result = campaign.getChannel() == Channel.EMAIL
                    ? sendEmail(campaign, recipient, smtp)
                    : sendSms(campaign, recipient, sms);

            recipient.setSentAt(Instant.now());
            if (result.ok()) {
                recipient.setStatus(RecipientStatus.SENT);
                recipient.setProviderMessageId(result.providerMessageId());
                recipient.setError(null);
                sentNow++;
            } else {
                recipient.setStatus(RecipientStatus.FAILED);
                recipient.setError(result.error());
            }
            recipientRepository.save(recipient);
        }

        if (sentNow > 0) {
            campaign.setSentToday((campaign.getSentToday() != null ? campaign.getSentToday() : 0) + sentNow);
            campaignRepository.save(campaign);
        }

        // Zakonczenie kampanii, gdy nie ma juz nic w kolejce.
        if (recipientRepository.countByCampaignIdAndStatus(campaign.getId(), RecipientStatus.QUEUED) == 0) {
            campaign.setStatus(CampaignStatus.COMPLETED);
            campaignRepository.save(campaign);
        }
    }

    private SendResult sendEmail(Campaign campaign, CampaignRecipient recipient, SmtpConfig smtp) {
        String html = messageComposer.composeEmail(campaign.getBody(), recipient.getName(), recipient.getLeadId());
        return emailSender.send(smtp, recipient.getAddress(), campaign.getSubject(), html);
    }

    private SendResult sendSms(Campaign campaign, CampaignRecipient recipient, SmsGatewayConfig sms) {
        String text = messageComposer.composeSms(campaign.getBody(), recipient.getName(), recipient.getLeadId());
        return smsSender.send(sms, recipient.getAddress(), text);
    }
}
