package webui

import (
	"encoding/json"
	"net/http"
	"regexp"
	"strings"
	"time"

	"mailgoserver/internal/db"
	"mailgoserver/internal/dkim"
)

func writeJSON(w http.ResponseWriter, status int, v any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(v)
}

func isAjax(r *http.Request) bool {
	return r.Header.Get("X-Requested-With") == "XMLHttpRequest" || strings.Contains(r.Header.Get("Content-Type"), "application/json")
}

// dkimList mirrors dkim.py's dkim_list().
func (a *App) dkimList(w http.ResponseWriter, r *http.Request) {
	active, err := a.DB.ListActiveDKIMKeysWithDomain()
	if err != nil {
		setFlash(w, "error", "Error loading DKIM keys")
	}
	inactive, _ := a.DB.ListInactiveDKIMKeysWithDomain()
	publicIP := getPublicIP(a.Cfg)
	scope := scopeFromContext(r)

	var dkimData []M
	for _, k := range active {
		if !scope.Allowed(k.DomainID) {
			continue
		}
		rec, _ := a.DKIM.GetDKIMPublicKeyRecord(k.DomainName)
		if rec == nil {
			rec = &dkim.DNSRecord{}
		}
		spfCheck := checkDNSRecord(k.DomainName)
		existingSPF := ""
		for _, txt := range spfCheck.Records {
			if strings.Contains(txt, "v=spf1") {
				existingSPF = txt
				break
			}
		}
		dkimData = append(dkimData, M{
			"domain":          M{"id": k.DomainID, "domain_name": k.DomainName},
			"dkim_key":        k.DKIMKey,
			"dns_record":      M{"name": rec.Name, "value": rec.Value},
			"existing_spf":    existingSPF,
			"recommended_spf": generateSPFRecord(publicIP, existingSPF),
			"public_ip":       publicIP,
		})
	}
	var oldData []M
	for _, k := range inactive {
		if !scope.Allowed(k.DomainID) {
			continue
		}
		status := "Disabled"
		if k.ReplacedAt != nil {
			status = "Replaced"
		}
		oldData = append(oldData, M{
			"domain":      M{"id": k.DomainID, "domain_name": k.DomainName},
			"dkim_key":    k.DKIMKey,
			"status_text": status,
		})
	}

	a.render(w, r, "dkim.html", M{"active": "dkim", "dkim_data": dkimData, "old_dkim_data": oldData})
}

// createDKIM mirrors dkim.py's create_dkim().
func (a *App) createDKIM(w http.ResponseWriter, r *http.Request) {
	domain := r.FormValue("domain")
	selector := r.FormValue("selector")
	if domain == "" {
		writeJSON(w, http.StatusBadRequest, M{"success": false, "message": "Domain is required"})
		return
	}
	dom, err := a.DB.GetDomainByNameExact(domain)
	if err != nil || dom == nil {
		writeJSON(w, http.StatusNotFound, M{"success": false, "message": "Domain not found"})
		return
	}
	if !scopeFromContext(r).Allowed(dom.ID) {
		writeJSON(w, http.StatusNotFound, M{"success": false, "message": "Domain not found"})
		return
	}
	if err := a.DB.DeactivateActiveDKIMKeysForDomain(dom.ID, time.Now()); err != nil {
		writeJSON(w, http.StatusInternalServerError, M{"success": false, "message": "Failed to create DKIM key"})
		return
	}
	ok, err := a.DKIM.GenerateDKIMKeypair(domain, selector, true)
	if err != nil || !ok {
		writeJSON(w, http.StatusInternalServerError, M{"success": false, "message": "Failed to create DKIM key"})
		return
	}
	writeJSON(w, http.StatusOK, M{"success": true, "message": "DKIM key created successfully"})
}

// regenerateDKIM mirrors dkim.py's regenerate_dkim() — path id is a DOMAIN id.
func (a *App) regenerateDKIM(w http.ResponseWriter, r *http.Request) {
	domainID := pathID(r)
	dom, err := a.DB.GetDomainByID(domainID)
	if err != nil || dom == nil {
		http.NotFound(w, r)
		return
	}
	if !requireDomainAccess(w, r, dom.ID) {
		return
	}
	current, _ := a.DB.GetActiveDKIMKeyByDomainID(domainID)
	selector := ""
	if current != nil {
		selector = current.Selector
	}
	if err := a.DB.DeactivateActiveDKIMKeysForDomain(domainID, time.Now()); err != nil {
		a.dkimActionFailed(w, r, "Failed to regenerate DKIM key")
		return
	}
	ok, err := a.DKIM.GenerateDKIMKeypair(dom.DomainName, selector, true)
	if err != nil || !ok {
		a.dkimActionFailed(w, r, "Failed to regenerate DKIM key")
		return
	}

	if isAjax(r) {
		publicIP := getPublicIP(a.Cfg)
		rec, _ := a.DKIM.GetDKIMPublicKeyRecord(dom.DomainName)
		spfCheck := checkDNSRecord(dom.DomainName)
		existingSPF := ""
		for _, txt := range spfCheck.Records {
			if strings.Contains(txt, "v=spf1") {
				existingSPF = txt
				break
			}
		}
		newKey, _ := a.DB.GetActiveDKIMKeyByDomainID(domainID)
		writeJSON(w, http.StatusOK, M{
			"success": true, "message": "DKIM key regenerated successfully",
			"new_key": newKey, "dns_record": rec, "existing_spf": existingSPF,
			"recommended_spf": generateSPFRecord(publicIP, existingSPF),
			"public_ip":       publicIP, "domain": dom.DomainName,
		})
		return
	}
	setFlash(w, "success", "DKIM key regenerated successfully")
	http.Redirect(w, r, Prefix+"/dkim", http.StatusFound)
}

func (a *App) dkimActionFailed(w http.ResponseWriter, r *http.Request, message string) {
	if isAjax(r) {
		writeJSON(w, http.StatusInternalServerError, M{"success": false, "message": message})
		return
	}
	setFlash(w, "error", message)
	http.Redirect(w, r, Prefix+"/dkim", http.StatusFound)
}

func (a *App) editDKIMForm(w http.ResponseWriter, r *http.Request) {
	key, ok := a.dkimKeyWithAccess(w, r)
	if !ok {
		return
	}
	dom, _ := a.DB.GetDomainByID(key.DomainID)
	a.render(w, r, "edit_dkim.html", M{"active": "dkim", "dkim_key": key, "domain": dom})
}

var selectorPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)

// dkimKeyWithAccess fetches a DKIM key by path ID and confirms it belongs to a domain
// the current admin can manage.
func (a *App) dkimKeyWithAccess(w http.ResponseWriter, r *http.Request) (key *db.DKIMKey, ok bool) {
	key, err := a.DB.GetDKIMKeyByID(pathID(r))
	if err != nil || key == nil {
		http.NotFound(w, r)
		return nil, false
	}
	if !requireDomainAccess(w, r, key.DomainID) {
		return nil, false
	}
	return key, true
}

// editDKIM mirrors dkim.py's edit_dkim() POST branch.
func (a *App) editDKIM(w http.ResponseWriter, r *http.Request) {
	key, ok := a.dkimKeyWithAccess(w, r)
	if !ok {
		return
	}
	id := key.ID
	dom, _ := a.DB.GetDomainByID(key.DomainID)
	selector := strings.TrimSpace(r.FormValue("selector"))

	if selector == "" || !selectorPattern.MatchString(selector) {
		setFlash(w, "error", "Selector must contain only letters, numbers, hyphens, and underscores")
		a.render(w, r, "edit_dkim.html", M{"active": "dkim", "dkim_key": key, "domain": dom})
		return
	}
	if exists, _ := a.DB.SelectorExistsForDomain(key.DomainID, selector, id); exists {
		setFlash(w, "error", "This selector is already in use for this domain")
		a.render(w, r, "edit_dkim.html", M{"active": "dkim", "dkim_key": key, "domain": dom})
		return
	}
	if err := a.DB.UpdateDKIMKeySelector(id, selector); err != nil {
		setFlash(w, "error", "Error updating selector")
		http.Redirect(w, r, Prefix+"/dkim", http.StatusFound)
		return
	}
	setFlash(w, "success", "DKIM selector updated successfully")
	http.Redirect(w, r, Prefix+"/dkim", http.StatusFound)
}

// toggleDKIM mirrors dkim.py's toggle_dkim().
func (a *App) toggleDKIM(w http.ResponseWriter, r *http.Request) {
	key, ok := a.dkimKeyWithAccess(w, r)
	if !ok {
		return
	}
	id := key.ID
	newActive := !key.IsActive
	if newActive {
		if err := a.DB.DeactivateActiveDKIMKeysForDomain(key.DomainID, time.Now()); err != nil {
			a.dkimActionFailed(w, r, "Error toggling DKIM status")
			return
		}
	}
	if err := a.DB.SetDKIMKeyActive(id, newActive, time.Now()); err != nil {
		a.dkimActionFailed(w, r, "Error toggling DKIM status")
		return
	}
	message := "DKIM key disabled"
	if newActive {
		message = "DKIM key enabled"
	}
	if isAjax(r) {
		writeJSON(w, http.StatusOK, M{"success": true, "message": message, "is_active": newActive})
		return
	}
	setFlash(w, "success", message)
	http.Redirect(w, r, Prefix+"/dkim", http.StatusFound)
}

func (a *App) removeDKIM(w http.ResponseWriter, r *http.Request) {
	key, ok := a.dkimKeyWithAccess(w, r)
	if !ok {
		return
	}
	if err := a.DB.RemoveDKIMKey(key.ID); err != nil {
		setFlash(w, "error", "Error removing DKIM key")
	} else {
		setFlash(w, "success", "DKIM key permanently removed")
	}
	http.Redirect(w, r, Prefix+"/dkim", http.StatusFound)
}

// checkDKIMDNS mirrors dkim.py's check_dkim_dns().
func (a *App) checkDKIMDNS(w http.ResponseWriter, r *http.Request) {
	domain := r.FormValue("domain")
	selector := r.FormValue("selector")
	rec, err := a.DKIM.GetDKIMPublicKeyRecord(domain)
	if err != nil || rec == nil {
		writeJSON(w, http.StatusOK, M{"success": false, "message": "No active DKIM key for domain"})
		return
	}
	dnsName := selector + "._domainkey." + domain
	result := checkDNSRecord(dnsName)
	found := false
	expected := strings.Trim(rec.Value, `"`)
	for _, txt := range result.Records {
		if strings.Contains(txt, expected) || strings.Contains(expected, txt) {
			found = true
			break
		}
	}
	message := "DKIM record not found or does not match"
	if found {
		message = "DKIM record found and matches"
	} else if !result.Success {
		message = result.Message
	}
	writeJSON(w, http.StatusOK, M{"success": found, "message": message, "records": result.Records})
}

// checkSPFDNS mirrors dkim.py's check_spf_dns().
func (a *App) checkSPFDNS(w http.ResponseWriter, r *http.Request) {
	domain := r.FormValue("domain")
	result := checkDNSRecord(domain)
	var spfRecord string
	for _, txt := range result.Records {
		if strings.Contains(txt, "v=spf1") {
			spfRecord = txt
			break
		}
	}
	publicIP := getPublicIP(a.Cfg)
	validForServer := spfRecord != "" && strings.Contains(spfRecord, "ip4:"+publicIP)
	message := "SPF record not found"
	if spfRecord != "" {
		message = "SPF record found"
	}
	writeJSON(w, http.StatusOK, M{
		"success": spfRecord != "", "message": message, "records": result.Records,
		"spf_record": spfRecord, "spf_valid_for_server": validForServer,
		"spf_check_message": message, "public_ip": publicIP,
	})
}
