package pl.polskalokalnie.unsubscribe;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Locale;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import pl.polskalokalnie.lead.Lead;
import pl.polskalokalnie.lead.LeadRepository;
import pl.polskalokalnie.lead.LeadStatus;

/**
 * Generuje i weryfikuje podpisane (HMAC-SHA256) tokeny rezygnacji doklejane do kazdej wiadomosci
 * marketingowej. Rezygnacja jest trwala: ustawia opt-out i zdejmuje zgode na danym kanale, dzieki
 * czemu kontakt nie trafi do zadnej kolejnej kampanii.
 */
@Service
public class UnsubscribeService {

    private static final String HMAC_ALGO = "HmacSHA256";
    private static final Base64.Encoder URL_ENCODER = Base64.getUrlEncoder().withoutPadding();
    private static final Base64.Decoder URL_DECODER = Base64.getUrlDecoder();

    private final LeadRepository leadRepository;
    private final byte[] secret;

    public UnsubscribeService(LeadRepository leadRepository, @Value("${app.jwt.secret}") String secret) {
        this.leadRepository = leadRepository;
        this.secret = secret.getBytes(StandardCharsets.UTF_8);
    }

    /** Token dla leada na danym kanale ("email" albo "sms"). */
    public String generateToken(Long leadId, String channel) {
        String payload = leadId + ":" + normalizeChannel(channel);
        String encodedPayload = URL_ENCODER.encodeToString(payload.getBytes(StandardCharsets.UTF_8));
        return encodedPayload + "." + sign(encodedPayload);
    }

    @Transactional
    public Result unsubscribe(String token) {
        if (token == null || !token.contains(".")) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Nieprawidłowy link rezygnacji");
        }
        int dot = token.lastIndexOf('.');
        String encodedPayload = token.substring(0, dot);
        String signature = token.substring(dot + 1);
        if (!sign(encodedPayload).equals(signature)) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Link rezygnacji jest nieprawidłowy lub wygasł");
        }

        String payload = new String(URL_DECODER.decode(encodedPayload), StandardCharsets.UTF_8);
        String[] parts = payload.split(":", 2);
        if (parts.length != 2) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Nieprawidłowy link rezygnacji");
        }
        Long leadId = parseLeadId(parts[0]);
        String channel = normalizeChannel(parts[1]);

        Lead lead = leadRepository.findById(leadId)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Kontakt nie istnieje"));

        if ("sms".equals(channel)) {
            lead.setSmsOptOut(true);
            lead.setSmsConsent(false);
        } else {
            lead.setEmailOptOut(true);
            lead.setEmailConsent(false);
        }
        lead.setStatus(LeadStatus.UNSUBSCRIBED);
        leadRepository.save(lead);
        return new Result(channel, maskContact(lead, channel));
    }

    private String sign(String data) {
        try {
            Mac mac = Mac.getInstance(HMAC_ALGO);
            mac.init(new SecretKeySpec(secret, HMAC_ALGO));
            byte[] raw = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
            return URL_ENCODER.encodeToString(raw);
        } catch (Exception ex) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "Błąd podpisu tokenu");
        }
    }

    private static Long parseLeadId(String value) {
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException ex) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Nieprawidłowy link rezygnacji");
        }
    }

    private static String normalizeChannel(String channel) {
        return channel != null && channel.trim().toLowerCase(Locale.ROOT).startsWith("sms") ? "sms" : "email";
    }

    private static String maskContact(Lead lead, String channel) {
        String value = "sms".equals(channel) ? lead.getPhone() : lead.getEmail();
        if (value == null || value.length() < 4) {
            return "";
        }
        return value.substring(0, 2) + "***" + value.substring(value.length() - 2);
    }

    public record Result(String channel, String maskedContact) {
    }
}
