package webui

import (
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"mailgoserver/internal/acmecert"
)

// leAlwaysOverwriteFields are plain (non-secret) [LetsEncrypt] (DNS-01) settings —
// always persisted from the submitted form, same as any other settings.html field.
var leAlwaysOverwriteFields = []string{
	"enabled", "staging", "contact_email", "domains", "dns_provider",
	"route53_region", "route53_hosted_zone_id", "gcloud_project",
}

// leHTTPFields are the [LetsEncryptHTTP] (HTTP-01) settings, all plain — no secrets to
// redact, unlike the DNS-01 providers' API credentials.
var leHTTPFields = []string{
	"enabled", "staging", "contact_email", "domains", "include_ip", "ip_override",
}

// leSecretFields hold DNS provider credentials. They're never rendered back into the
// form (always blank) and the save handler only overwrites the stored value when the
// submitted field is non-empty — "leave blank to keep the current value", the same
// idiom edit_sender.html already uses for its password field.
var leSecretFields = []string{
	"cloudflare_api_token", "route53_access_key_id", "route53_secret_access_key",
	"digitalocean_api_token", "gcloud_service_account_json_path",
}

// letsEncryptPage shows the current Let's Encrypt status and configuration forms for
// both the DNS-01 and HTTP-01 managers. Secret fields are always blank in the rendered
// form — see leSecretFields.
func (a *App) letsEncryptPage(w http.ResponseWriter, r *http.Request) {
	sec := a.Cfg.Section("LetsEncrypt")
	kv := M{}
	for _, k := range leAlwaysOverwriteFields {
		kv[k] = sec.Key(k).String()
	}
	for _, k := range leSecretFields {
		kv[k] = ""
	}

	httpSec := a.Cfg.Section("LetsEncryptHTTP")
	httpKV := M{}
	for _, k := range leHTTPFields {
		httpKV[k] = httpSec.Key(k).String()
	}

	a.render(w, r, "letsencrypt.html", M{
		"active": "letsencrypt",
		"le": kv, "status": a.ACME.Status(),
		"leHTTP": httpKV, "statusHTTP": a.ACMEHTTP.Status(),
	})
}

// letsEncryptSave is a dedicated handler (not the generic settingsUpdate reflection)
// specifically because of leSecretFields' blank-means-keep-existing semantics —
// settingsUpdate would otherwise blank out a stored credential whenever this form is
// submitted with a secret field left empty.
func (a *App) letsEncryptSave(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		setFlash(w, "error", "Invalid form data")
		http.Redirect(w, r, Prefix+"/letsencrypt", http.StatusFound)
		return
	}
	sec := a.Cfg.Section("LetsEncrypt")
	for _, k := range leAlwaysOverwriteFields {
		sec.Key(k).SetValue(r.FormValue(k))
	}
	for _, k := range leSecretFields {
		if v := r.FormValue(k); v != "" {
			sec.Key(k).SetValue(v)
		}
	}
	if err := a.Cfg.SaveTo(a.ConfigPath); err != nil {
		setFlash(w, "error", "Error saving settings: "+err.Error())
		http.Redirect(w, r, Prefix+"/letsencrypt", http.StatusFound)
		return
	}
	setFlash(w, "success", `Let's Encrypt settings saved. Use "Obtain / Renew Now" to test the configuration.`)
	http.Redirect(w, r, Prefix+"/letsencrypt", http.StatusFound)
}

// letsEncryptObtainNow triggers an immediate obtain/renew — separate from Save, since
// saving configuration must never silently kick off an ACME transaction as a side
// effect. This is also how the very first certificate actually gets obtained.
func (a *App) letsEncryptObtainNow(w http.ResponseWriter, r *http.Request) {
	if err := a.ACME.ObtainOrRenew(r.Context()); err != nil {
		setFlash(w, "error", "Could not obtain certificate: "+err.Error())
	} else {
		setFlash(w, "success", "Certificate obtained successfully")
	}
	http.Redirect(w, r, Prefix+"/letsencrypt", http.StatusFound)
}

// letsEncryptHTTPSave mirrors letsEncryptSave for the [LetsEncryptHTTP] section — a
// separate handler (not the generic settingsUpdate reflection) purely so it can
// redirect back to /letsencrypt like its DNS-01 sibling; every field here is plain, so
// unlike letsEncryptSave there's no secret-redaction concern.
func (a *App) letsEncryptHTTPSave(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseForm(); err != nil {
		setFlash(w, "error", "Invalid form data")
		http.Redirect(w, r, Prefix+"/letsencrypt", http.StatusFound)
		return
	}
	sec := a.Cfg.Section("LetsEncryptHTTP")
	for _, k := range leHTTPFields {
		sec.Key(k).SetValue(r.FormValue(k))
	}
	if err := a.Cfg.SaveTo(a.ConfigPath); err != nil {
		setFlash(w, "error", "Error saving settings: "+err.Error())
		http.Redirect(w, r, Prefix+"/letsencrypt", http.StatusFound)
		return
	}
	setFlash(w, "success", `Let's Encrypt HTTP-01 settings saved. If you just changed "Enable HTTP-01" or the port, restart the server before using "Obtain / Renew Now" — the challenge responder only starts at boot.`)
	http.Redirect(w, r, Prefix+"/letsencrypt", http.StatusFound)
}

// letsEncryptHTTPObtainNow mirrors letsEncryptObtainNow for the HTTP-01 manager.
func (a *App) letsEncryptHTTPObtainNow(w http.ResponseWriter, r *http.Request) {
	if err := a.ACMEHTTP.ObtainOrRenew(r.Context()); err != nil {
		setFlash(w, "error", "Could not obtain certificate: "+err.Error())
	} else {
		setFlash(w, "success", "Certificate obtained successfully")
	}
	http.Redirect(w, r, Prefix+"/letsencrypt", http.StatusFound)
}

// uploadGCloudServiceAccount mirrors settings.go's uploadTLSFile two-step flow: upload
// the file, return its saved path as JSON, and the browser fills a sibling text input
// with that path — the path only actually persists once the surrounding form (Save)
// is submitted.
func (a *App) uploadGCloudServiceAccount(w http.ResponseWriter, r *http.Request) {
	if err := r.ParseMultipartForm(10 << 20); err != nil {
		writeJSON(w, http.StatusBadRequest, M{"status": "error", "message": "Invalid upload"})
		return
	}
	file, header, err := r.FormFile("gcloud_key_file")
	if err != nil {
		writeJSON(w, http.StatusBadRequest, M{"status": "error", "message": "No file provided"})
		return
	}
	defer file.Close()
	if ext := strings.ToLower(filepath.Ext(header.Filename)); ext != ".json" {
		writeJSON(w, http.StatusBadRequest, M{"status": "error", "message": "Expected a .json service account key file"})
		return
	}
	acmeDir := filepath.Join(filepath.Dir(a.ConfigPath), "server_data", "acme")
	os.MkdirAll(acmeDir, 0o755)
	filePath := filepath.Join(acmeDir, fmt.Sprintf("gcloud-sa-%d.json", time.Now().Unix()))
	out, err := os.Create(filePath)
	if err != nil {
		writeJSON(w, http.StatusInternalServerError, M{"status": "error", "message": "Could not save file"})
		return
	}
	defer out.Close()
	if _, err := out.ReadFrom(file); err != nil {
		writeJSON(w, http.StatusInternalServerError, M{"status": "error", "message": "Could not save file"})
		return
	}
	writeJSON(w, http.StatusOK, M{"status": "success", "filepath": filePath})
}

// detectWANIP backs the "Detect" button next to the HTTP-01 manual IP override field —
// a live lookup, not persisted anywhere until the surrounding form is saved.
func (a *App) detectWANIP(w http.ResponseWriter, r *http.Request) {
	ip, err := acmecert.DetectWANIP(r.Context())
	if err != nil {
		writeJSON(w, http.StatusBadGateway, M{"status": "error", "message": err.Error()})
		return
	}
	writeJSON(w, http.StatusOK, M{"status": "success", "ip": ip})
}
