package pl.polskalokalnie.lead;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

/**
 * Logika bazy leadow: listowanie z filtrami, reczne dodawanie, import listy, edycja i usuwanie.
 * Deduplikacja po e-mailu i telefonie, dzieki czemu import i synchronizacja userow nie tworza
 * podwojnych kontaktow.
 */
@Service
public class LeadService {

    private final LeadRepository leadRepository;

    public LeadService(LeadRepository leadRepository) {
        this.leadRepository = leadRepository;
    }

    public List<LeadResponse> list(LeadSource source, String search) {
        String needle = search == null ? "" : search.trim().toLowerCase(Locale.ROOT);
        return leadRepository.findAll().stream()
                .filter(lead -> source == null || lead.getSource() == source)
                .filter(lead -> needle.isEmpty() || matchesSearch(lead, needle))
                .sorted(Comparator.comparing(Lead::getCreatedAt).reversed())
                .map(LeadResponse::from)
                .toList();
    }

    @Transactional
    public LeadResponse create(LeadCreateRequest request) {
        String email = normalizeEmail(request.email());
        String phone = normalizePhone(request.phone());
        if (email == null && phone == null) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Podaj co najmniej e-mail lub telefon");
        }
        if (email != null && leadRepository.findFirstByEmailIgnoreCase(email).isPresent()) {
            throw new ResponseStatusException(HttpStatus.CONFLICT, "Lead z tym adresem e-mail już istnieje");
        }
        if (phone != null && leadRepository.findFirstByPhone(phone).isPresent()) {
            throw new ResponseStatusException(HttpStatus.CONFLICT, "Lead z tym numerem telefonu już istnieje");
        }

        Lead lead = new Lead();
        lead.setSource(LeadSource.MANUAL);
        lead.setName(trimToNull(request.name()));
        lead.setEmail(email);
        lead.setPhone(phone);
        lead.setCity(trimToNull(request.city()));
        lead.setTags(cleanTags(request.tags()));
        lead.setEmailConsent(request.emailConsent() == null || request.emailConsent());
        lead.setSmsConsent(request.smsConsent() == null || request.smsConsent());
        lead.setNotes(trimToNull(request.notes()));
        return LeadResponse.from(leadRepository.save(lead));
    }

    @Transactional
    public LeadResponse update(Long id, LeadUpdateRequest request) {
        Lead lead = requireLead(id);
        if (request.name() != null) {
            lead.setName(trimToNull(request.name()));
        }
        if (request.email() != null) {
            String email = normalizeEmail(request.email());
            if (email != null) {
                leadRepository.findFirstByEmailIgnoreCase(email)
                        .filter(other -> !other.getId().equals(lead.getId()))
                        .ifPresent(other -> {
                            throw new ResponseStatusException(HttpStatus.CONFLICT, "Inny lead ma już ten e-mail");
                        });
            }
            lead.setEmail(email);
        }
        if (request.phone() != null) {
            lead.setPhone(normalizePhone(request.phone()));
        }
        if (request.city() != null) {
            lead.setCity(trimToNull(request.city()));
        }
        if (request.status() != null) {
            lead.setStatus(request.status());
        }
        if (request.tags() != null) {
            lead.setTags(cleanTags(request.tags()));
        }
        if (request.emailConsent() != null) {
            lead.setEmailConsent(request.emailConsent());
        }
        if (request.smsConsent() != null) {
            lead.setSmsConsent(request.smsConsent());
        }
        // Opt-out to trwala rezygnacja - ustawiamy wylacznie na true, nie cofamy z panelu.
        if (Boolean.TRUE.equals(request.emailOptOut())) {
            lead.setEmailOptOut(true);
        }
        if (Boolean.TRUE.equals(request.smsOptOut())) {
            lead.setSmsOptOut(true);
        }
        if (request.notes() != null) {
            lead.setNotes(trimToNull(request.notes()));
        }
        return LeadResponse.from(leadRepository.save(lead));
    }

    @Transactional
    public void delete(Long id) {
        Lead lead = requireLead(id);
        leadRepository.delete(lead);
    }

    @Transactional
    public ImportResultResponse importCsv(LeadImportRequest request) {
        List<String> messages = new ArrayList<>();
        int imported = 0;
        int skipped = 0;
        String defaultTag = trimToNull(request.defaultTag());

        String[] lines = request.csv().replace("\r\n", "\n").replace("\r", "\n").split("\n");
        int lineNumber = 0;
        for (String rawLine : lines) {
            lineNumber++;
            String line = rawLine.trim();
            if (line.isEmpty()) {
                continue;
            }
            String[] parts = line.split("[;,\t]", -1);
            String header = parts.length > 0 ? parts[0].trim().toLowerCase(Locale.ROOT) : "";
            // Pomijamy wiersz naglowka, jesli wyglada na etykiety kolumn.
            if (lineNumber == 1 && (header.equals("name") || header.equals("imie") || header.equals("imię")
                    || header.equals("nazwa") || header.contains("email") || header.contains("e-mail"))) {
                continue;
            }

            String name = parts.length > 0 ? trimToNull(parts[0]) : null;
            String email = normalizeEmail(parts.length > 1 ? parts[1] : null);
            String phone = normalizePhone(parts.length > 2 ? parts[2] : null);
            String city = parts.length > 3 ? trimToNull(parts[3]) : null;

            if (email == null && phone == null) {
                skipped++;
                messages.add("Wiersz " + lineNumber + ": pominięto (brak e-maila i telefonu)");
                continue;
            }
            if (email != null && leadRepository.findFirstByEmailIgnoreCase(email).isPresent()) {
                skipped++;
                messages.add("Wiersz " + lineNumber + ": pominięto (e-mail już istnieje)");
                continue;
            }
            if (phone != null && leadRepository.findFirstByPhone(phone).isPresent()) {
                skipped++;
                messages.add("Wiersz " + lineNumber + ": pominięto (telefon już istnieje)");
                continue;
            }

            Lead lead = new Lead();
            lead.setSource(LeadSource.IMPORT);
            lead.setName(name);
            lead.setEmail(email);
            lead.setPhone(phone);
            lead.setCity(city);
            if (defaultTag != null) {
                lead.setTags(new ArrayList<>(List.of(defaultTag)));
            }
            leadRepository.save(lead);
            imported++;
        }
        return new ImportResultResponse(imported, skipped, messages);
    }

    private Lead requireLead(Long id) {
        return leadRepository.findById(id)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Lead nie istnieje"));
    }

    private static boolean matchesSearch(Lead lead, String needle) {
        return contains(lead.getName(), needle)
                || contains(lead.getEmail(), needle)
                || contains(lead.getPhone(), needle)
                || contains(lead.getCity(), needle);
    }

    private static boolean contains(String value, String needle) {
        return value != null && value.toLowerCase(Locale.ROOT).contains(needle);
    }

    private static List<String> cleanTags(List<String> tags) {
        List<String> cleaned = new ArrayList<>();
        if (tags != null) {
            Set<String> seen = new LinkedHashSet<>();
            for (String tag : tags) {
                String trimmed = trimToNull(tag);
                if (trimmed != null && seen.add(trimmed.toLowerCase(Locale.ROOT))) {
                    cleaned.add(trimmed);
                }
            }
        }
        return cleaned;
    }

    private static String normalizeEmail(String email) {
        String trimmed = trimToNull(email);
        return trimmed == null ? null : trimmed.toLowerCase(Locale.ROOT);
    }

    private static String normalizePhone(String phone) {
        if (phone == null) {
            return null;
        }
        String cleaned = phone.replaceAll("[\\s\\-()]", "").trim();
        return cleaned.isEmpty() ? null : cleaned;
    }

    private static String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String trimmed = value.trim();
        return trimmed.isEmpty() ? null : trimmed;
    }
}
