package de.cannamanage.service;

import de.cannamanage.domain.entity.CustomMailDomain;
import de.cannamanage.service.repository.CustomMailDomainRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.time.Instant;
import java.util.Hashtable;
import java.util.Optional;
import java.util.UUID;

/**
 * Service for managing custom mail domains for Enterprise tier clubs.
 * 
 * Verification flow:
 * 1. Admin enters desired FROM address (e.g., info@gruener-daumen.de)
 * 2. System extracts domain, generates unique verification token
 * 3. Admin adds DNS TXT record: cannamanage-verify={token}
 * 4. Admin clicks "Verify" → system does DNS TXT lookup
 * 5. If record matches → mark as verified
 * 6. Outbound emails now use the custom FROM address
 */
@Slf4j
@Service
@Transactional
public class CustomMailDomainService {

    private static final String DNS_TXT_PREFIX = "cannamanage-verify=";

    private final CustomMailDomainRepository repository;
    private final PlanTierService planTierService;

    public CustomMailDomainService(CustomMailDomainRepository repository,
                                   PlanTierService planTierService) {
        this.repository = repository;
        this.planTierService = planTierService;
    }

    /**
     * Set a custom FROM address for an Enterprise club.
     * Generates a verification token that must be added as DNS TXT record.
     */
    public CustomMailDomain setCustomDomain(UUID tenantId, String fromAddress) {
        planTierService.requireEnterpriseTier(tenantId);

        String domain = extractDomain(fromAddress);
        String token = UUID.randomUUID().toString().replace("-", "").substring(0, 32);

        // Remove existing config if any
        repository.findByTenantId(tenantId).ifPresent(repository::delete);

        CustomMailDomain config = new CustomMailDomain(tenantId, fromAddress, domain, token);
        CustomMailDomain saved = repository.save(config);

        log.info("Custom mail domain set for club {}: {} (domain: {})", tenantId, fromAddress, domain);
        return saved;
    }

    /**
     * Get the current custom domain configuration for a club.
     */
    @Transactional(readOnly = true)
    public Optional<CustomMailDomain> getCustomDomain(UUID tenantId) {
        return repository.findByTenantId(tenantId);
    }

    /**
     * Verify the custom domain by checking DNS TXT records.
     * Looks for: cannamanage-verify={token}
     */
    public CustomMailDomain verifyDomain(UUID tenantId) {
        planTierService.requireEnterpriseTier(tenantId);

        CustomMailDomain config = repository.findByTenantId(tenantId)
                .orElseThrow(() -> new IllegalStateException("No custom domain configured for this club"));

        if (config.isVerified()) {
            return config; // Already verified
        }

        boolean verified = checkDnsTxtRecord(config.getDomain(), config.getVerificationToken());

        if (verified) {
            config.setVerified(true);
            config.setVerifiedAt(Instant.now());
            repository.save(config);
            log.info("Custom mail domain verified for club {}: {}", tenantId, config.getDomain());
        } else {
            log.info("DNS verification failed for club {} domain {}", tenantId, config.getDomain());
            throw new IllegalStateException(
                    "DNS-Verifikation fehlgeschlagen. Bitte stelle sicher, dass der TXT-Eintrag " +
                    "'" + DNS_TXT_PREFIX + config.getVerificationToken() + "' " +
                    "für die Domain '" + config.getDomain() + "' korrekt gesetzt ist. " +
                    "DNS-Änderungen können bis zu 48 Stunden dauern."
            );
        }

        return config;
    }

    /**
     * Remove custom domain configuration.
     */
    public void removeCustomDomain(UUID tenantId) {
        repository.findByTenantId(tenantId).ifPresent(config -> {
            repository.delete(config);
            log.info("Custom mail domain removed for club {}", tenantId);
        });
    }

    /**
     * Get the effective FROM address for a club's outbound emails.
     * Returns custom FROM if verified, otherwise platform default.
     */
    @Transactional(readOnly = true)
    public Optional<String> getVerifiedFromAddress(UUID tenantId) {
        return repository.findByTenantId(tenantId)
                .filter(CustomMailDomain::isVerified)
                .map(CustomMailDomain::getFromAddress);
    }

    /**
     * Check DNS TXT records for the verification token.
     */
    private boolean checkDnsTxtRecord(String domain, String expectedToken) {
        try {
            Hashtable<String, String> env = new Hashtable<>();
            env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
            DirContext ctx = new InitialDirContext(env);

            Attributes attrs = ctx.getAttributes(domain, new String[]{"TXT"});
            Attribute txtAttr = attrs.get("TXT");

            if (txtAttr != null) {
                NamingEnumeration<?> records = txtAttr.getAll();
                while (records.hasMore()) {
                    String record = records.next().toString().replace("\"", "").trim();
                    if (record.equals(DNS_TXT_PREFIX + expectedToken)) {
                        ctx.close();
                        return true;
                    }
                }
            }

            ctx.close();
        } catch (Exception e) {
            log.warn("DNS lookup failed for domain {}: {}", domain, e.getMessage());
        }
        return false;
    }

    private String extractDomain(String email) {
        if (email == null || !email.contains("@")) {
            throw new IllegalArgumentException("Invalid email address: " + email);
        }
        return email.substring(email.indexOf("@") + 1).toLowerCase();
    }
}
