package webui

import (
	"net/http"
	"regexp"
	"strings"

	"mailgoserver/internal/db"
)

// validLocalPart mirrors the RFC-5321-ish practical subset most mail systems accept
// for the part of an address before "@" — letters, digits, and . _ % + -.
var validLocalPart = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+$`)

// buildSenderEmail resolves the domain by ID (so the client can't just type an
// arbitrary domain string) and joins it with the local part — this is what actually
// fixes "any text accepted, then assigned to an unrelated domain": the address is
// always <local_part>@<selected domain's real name>, never free text.
func (a *App) buildSenderEmail(localPart string, domainID int64) (string, error) {
	dom, err := a.DB.GetDomainByID(domainID)
	if err != nil {
		return "", err
	}
	if dom == nil || !validLocalPart.MatchString(localPart) {
		return "", nil
	}
	return localPart + "@" + dom.DomainName, nil
}

func localPartOf(email string) string {
	if i := strings.Index(email, "@"); i >= 0 {
		return email[:i]
	}
	return email
}

func (a *App) sendersList(w http.ResponseWriter, r *http.Request) {
	senders, err := a.DB.ListSenders()
	if err != nil {
		setFlash(w, "error", "Error loading senders")
	}
	scope := scopeFromContext(r)
	// Templates use {% for sender, domain in senders %} — pair each row up as [sender, domain].
	var pairs [][2]any
	for _, s := range senders {
		if !scope.Allowed(s.DomainID) {
			continue
		}
		pairs = append(pairs, [2]any{s.Sender, M{"domain_name": s.DomainName}})
	}
	a.render(w, r, "senders.html", M{"active": "senders", "senders": pairs, "users": pairs})
}

func (a *App) addSenderForm(w http.ResponseWriter, r *http.Request) {
	domains, _ := a.accessibleDomains(r)
	a.render(w, r, "add_sender.html", M{"active": "senders", "domains": domains})
}

// addSender mirrors senders.py's add_sender() POST branch. The email is always
// <local_part>@<selected domain>, built server-side from the resolved domain name —
// see buildSenderEmail — so a sender can never end up assigned to a domain its own
// address doesn't belong to.
func (a *App) addSender(w http.ResponseWriter, r *http.Request) {
	localPart := strings.TrimSpace(r.FormValue("local_part"))
	password := r.FormValue("password")
	domainID := int64(atoi(r.FormValue("domain_id")))
	canSendAsDomain := r.FormValue("can_send_as_domain") == "on"
	storeMessage := r.FormValue("store_message_content") == "on"

	if !requireDomainAccess(w, r, domainID) {
		return
	}
	email, err := a.buildSenderEmail(localPart, domainID)
	if err != nil {
		setFlash(w, "error", "Error creating sender")
		http.Redirect(w, r, Prefix+"/senders/add", http.StatusFound)
		return
	}
	if email == "" || password == "" {
		setFlash(w, "error", "All fields are required and the local part may only contain letters, numbers, and . _ % + -")
		http.Redirect(w, r, Prefix+"/senders/add", http.StatusFound)
		return
	}
	if exists, _ := a.DB.EmailExists(email, -1); exists {
		setFlash(w, "error", "A sender with this email already exists")
		http.Redirect(w, r, Prefix+"/senders/add", http.StatusFound)
		return
	}
	hash, err := db.HashPassword(password)
	if err != nil {
		setFlash(w, "error", "Error creating sender")
		http.Redirect(w, r, Prefix+"/senders/add", http.StatusFound)
		return
	}
	if _, err := a.DB.CreateSender(email, hash, domainID, canSendAsDomain, storeMessage); err != nil {
		setFlash(w, "error", "Error creating sender")
		http.Redirect(w, r, Prefix+"/senders/add", http.StatusFound)
		return
	}
	setFlash(w, "success", "Sender added successfully")
	http.Redirect(w, r, Prefix+"/senders", http.StatusFound)
}

// senderWithAccess fetches a sender by path ID and confirms it belongs to a domain the
// current admin can manage, writing 404 and returning ok=false otherwise.
func (a *App) senderWithAccess(w http.ResponseWriter, r *http.Request) (sender *db.Sender, ok bool) {
	sender, err := a.DB.GetSenderByID(pathID(r))
	if err != nil || sender == nil {
		http.NotFound(w, r)
		return nil, false
	}
	if !requireDomainAccess(w, r, sender.DomainID) {
		return nil, false
	}
	return sender, true
}

func (a *App) disableSender(w http.ResponseWriter, r *http.Request) {
	sender, ok := a.senderWithAccess(w, r)
	if !ok {
		return
	}
	if err := a.DB.SetSenderActive(sender.ID, false); err != nil {
		setFlash(w, "error", "Error disabling sender")
	} else {
		setFlash(w, "success", "Sender disabled")
	}
	http.Redirect(w, r, Prefix+"/senders", http.StatusFound)
}

func (a *App) enableSender(w http.ResponseWriter, r *http.Request) {
	sender, ok := a.senderWithAccess(w, r)
	if !ok {
		return
	}
	if err := a.DB.SetSenderActive(sender.ID, true); err != nil {
		setFlash(w, "error", "Error enabling sender")
	} else {
		setFlash(w, "success", "Sender enabled")
	}
	http.Redirect(w, r, Prefix+"/senders", http.StatusFound)
}

func (a *App) removeSender(w http.ResponseWriter, r *http.Request) {
	sender, ok := a.senderWithAccess(w, r)
	if !ok {
		return
	}
	if err := a.DB.RemoveSender(sender.ID); err != nil {
		setFlash(w, "error", "Error removing sender")
	} else {
		setFlash(w, "success", "Sender permanently removed")
	}
	http.Redirect(w, r, Prefix+"/senders", http.StatusFound)
}

func (a *App) editSenderForm(w http.ResponseWriter, r *http.Request) {
	sender, ok := a.senderWithAccess(w, r)
	if !ok {
		return
	}
	domains, _ := a.accessibleDomains(r)
	a.render(w, r, "edit_sender.html", M{"active": "senders", "sender": sender, "domains": domains, "local_part": localPartOf(sender.Email)})
}

// editSender mirrors senders.py's edit_sender() POST branch. As in addSender, the
// email is rebuilt server-side from local_part + the resolved domain, never trusted
// as free text — see buildSenderEmail. Both the sender's current domain and the
// (possibly different) target domain from the form must be within the admin's scope.
func (a *App) editSender(w http.ResponseWriter, r *http.Request) {
	sender, ok := a.senderWithAccess(w, r)
	if !ok {
		return
	}
	id := sender.ID
	domains, _ := a.accessibleDomains(r)

	localPart := strings.TrimSpace(r.FormValue("local_part"))
	password := r.FormValue("password")
	domainID := int64(atoi(r.FormValue("domain_id")))
	canSendAsDomain := r.FormValue("can_send_as_domain") == "on"
	storeMessage := r.FormValue("store_message_content") == "on"

	renderErr := func(msg string) {
		setFlash(w, "error", msg)
		a.render(w, r, "edit_sender.html", M{"active": "senders", "sender": sender, "domains": domains, "local_part": localPart})
	}

	if !requireDomainAccess(w, r, domainID) {
		return
	}
	email, err := a.buildSenderEmail(localPart, domainID)
	if err != nil {
		renderErr("Error updating sender")
		return
	}
	if email == "" {
		renderErr("Please provide a valid local part (letters, numbers, and . _ % + - only) and domain")
		return
	}
	if exists, _ := a.DB.EmailExists(email, id); exists {
		renderErr("A sender with this email already exists")
		return
	}

	var hash string
	if password != "" {
		hash, err = db.HashPassword(password)
		if err != nil {
			setFlash(w, "error", "Error updating sender")
			http.Redirect(w, r, Prefix+"/senders", http.StatusFound)
			return
		}
	}
	if err := a.DB.UpdateSender(id, email, hash, domainID, canSendAsDomain, storeMessage); err != nil {
		setFlash(w, "error", "Error updating sender")
		http.Redirect(w, r, Prefix+"/senders", http.StatusFound)
		return
	}
	setFlash(w, "success", "Sender updated successfully")
	http.Redirect(w, r, Prefix+"/senders", http.StatusFound)
}
