package webui

import (
	"io"
	"net/http"
	"strings"

	"mailgoserver/internal/pgp"
)

func (a *App) webmailPGPGenerate(w http.ResponseWriter, r *http.Request) {
	mbox := mailboxFromContext(r)
	label := strings.TrimSpace(r.FormValue("label"))
	passphrase := r.FormValue("passphrase")
	if len(passphrase) < 8 {
		setFlash(w, "error", "Choose a passphrase of at least 8 characters — this is the only thing protecting the key, so make it a real one")
		http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
		return
	}
	if passphrase != r.FormValue("passphrase_confirm") {
		setFlash(w, "error", "Passphrases don't match")
		http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
		return
	}

	pubArmor, privArmor, err := pgp.GenerateKeyPair(mbox.Email, passphrase)
	if err == nil {
		err = a.storePGPIdentity(mbox.ID, label, mbox.Email, pubArmor, privArmor)
	}
	if err != nil {
		a.Logger.Error("pgp generate for mailbox %d: %v", mbox.ID, err)
		setFlash(w, "error", "Error generating PGP key")
	} else {
		setFlash(w, "success", "PGP key generated")
	}
	http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
}

// storePGPIdentity parses pubArmor for its fingerprint, then stores both halves —
// privArmor is expected already passphrase-protected by the caller (pgp.GenerateKeyPair
// or pgp.ImportPrivateKey both guarantee this).
func (a *App) storePGPIdentity(mailboxID int64, label, email string, pubArmor, privArmor []byte) error {
	pubEntity, err := pgp.ParsePublicKey(pubArmor)
	if err != nil {
		return err
	}
	fingerprint := pgp.Fingerprint(pubEntity)
	_, err = a.DB.CreatePGPIdentity(mailboxID, label, email, fingerprint, string(pubArmor), string(privArmor))
	return err
}

func (a *App) webmailPGPImport(w http.ResponseWriter, r *http.Request) {
	mbox := mailboxFromContext(r)
	if err := r.ParseMultipartForm(1 << 20); err != nil {
		setFlash(w, "error", "Error reading upload")
		http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
		return
	}
	label := strings.TrimSpace(r.FormValue("label"))
	passphrase := r.FormValue("passphrase")
	if passphrase == "" {
		setFlash(w, "error", "Enter the key's passphrase — its own, if it already has one, or a new one to protect it with if it doesn't")
		http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
		return
	}
	file, _, err := r.FormFile("key_file")
	if err != nil {
		setFlash(w, "error", "Please choose an armored PGP private key file (.asc)")
		http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
		return
	}
	defer file.Close()
	data, err := io.ReadAll(file)
	if err == nil {
		var pubArmor, privArmor []byte
		pubArmor, privArmor, err = pgp.ImportPrivateKey(data, passphrase)
		if err == nil {
			email := mbox.Email
			if entity, perr := pgp.ParsePublicKey(pubArmor); perr == nil {
				if id := entity.PrimaryIdentity(); id != nil && id.UserId != nil && id.UserId.Email != "" {
					email = id.UserId.Email
				}
			}
			err = a.storePGPIdentity(mbox.ID, label, email, pubArmor, privArmor)
		}
	}
	if err != nil {
		setFlash(w, "error", "Error importing PGP key: "+err.Error())
	} else {
		setFlash(w, "success", "PGP key imported")
	}
	http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
}

func (a *App) webmailPGPRemoveIdentity(w http.ResponseWriter, r *http.Request) {
	mbox := mailboxFromContext(r)
	identityID := int64(atoi(r.PathValue("identity_id")))
	if err := a.DB.DeletePGPIdentity(mbox.ID, identityID); err != nil {
		setFlash(w, "error", "Error removing PGP key")
	} else {
		setFlash(w, "success", "PGP key removed")
	}
	http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
}

func (a *App) webmailPGPDownloadKey(w http.ResponseWriter, r *http.Request) {
	mbox := mailboxFromContext(r)
	identityID := int64(atoi(r.PathValue("identity_id")))
	identity, err := a.DB.GetPGPIdentity(mbox.ID, identityID)
	if err != nil || identity == nil {
		http.NotFound(w, r)
		return
	}
	w.Header().Set("Content-Type", "application/pgp-keys")
	w.Header().Set("Content-Disposition", `attachment; filename="`+mbox.Email+`.asc"`)
	w.Write([]byte(identity.PublicKeyArmor))
}

// webmailPGPUnlock verifies a passphrase against one identity's stored private key
// and, on success, caches the unlocked entity for the rest of this login session
// (see webmail_pgp_cache.go) — the one interactive "enter your passphrase" flow in
// this codebase now, since S/MIME keys are stored plain and never need unlocking.
func (a *App) webmailPGPUnlock(w http.ResponseWriter, r *http.Request) {
	mbox := mailboxFromContext(r)
	next := r.FormValue("next")
	if !strings.HasPrefix(next, MailboxPrefix+"/") {
		next = MailboxPrefix + "/certs"
	}

	identityID := int64(atoi(r.FormValue("identity_id")))
	identity, err := a.DB.GetPGPIdentity(mbox.ID, identityID)
	if err != nil || identity == nil {
		setFlash(w, "error", "Unknown PGP key")
		http.Redirect(w, r, next, http.StatusFound)
		return
	}
	entity, err := pgp.ParsePrivateKey([]byte(identity.PrivateKeyArmor))
	if err != nil {
		a.Logger.Error("parse stored pgp key for identity %d: %v", identity.ID, err)
		setFlash(w, "error", "That key's stored data is corrupted")
		http.Redirect(w, r, next, http.StatusFound)
		return
	}
	if err := pgp.UnlockPrivateKey(entity, r.FormValue("passphrase")); err != nil {
		setFlash(w, "error", "Wrong passphrase")
		http.Redirect(w, r, next, http.StatusFound)
		return
	}
	a.pgpKeys.put(sessionToken(r), identity.ID, entity)
	setFlash(w, "success", "PGP key unlocked for this session")
	http.Redirect(w, r, next, http.StatusFound)
}

func (a *App) webmailPGPAddContact(w http.ResponseWriter, r *http.Request) {
	mbox := mailboxFromContext(r)
	if err := r.ParseMultipartForm(1 << 20); err != nil {
		setFlash(w, "error", "Error reading upload")
		http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
		return
	}
	email := strings.TrimSpace(r.FormValue("email"))
	label := strings.TrimSpace(r.FormValue("label"))
	file, _, err := r.FormFile("key_file")
	if email == "" || err != nil {
		setFlash(w, "error", "Please provide an email and a public key file")
		http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
		return
	}
	defer file.Close()
	data, err := io.ReadAll(file)
	var fingerprint string
	if err == nil {
		parsed, perr := pgp.ParsePublicKey(data)
		if perr != nil {
			err = perr
		} else {
			fingerprint = pgp.Fingerprint(parsed)
		}
	}
	if err != nil {
		setFlash(w, "error", "That doesn't look like a valid PGP public key file")
		http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
		return
	}
	if err := a.DB.UpsertPGPContact(mbox.ID, email, label, fingerprint, string(data)); err != nil {
		setFlash(w, "error", "Error saving contact key")
	} else {
		setFlash(w, "success", "Contact key added")
	}
	http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
}

func (a *App) webmailPGPRemoveContact(w http.ResponseWriter, r *http.Request) {
	mbox := mailboxFromContext(r)
	contactID := int64(atoi(r.PathValue("contact_id")))
	if err := a.DB.DeletePGPContact(mbox.ID, contactID); err != nil {
		setFlash(w, "error", "Error removing contact")
	} else {
		setFlash(w, "success", "Contact removed")
	}
	http.Redirect(w, r, MailboxPrefix+"/certs", http.StatusFound)
}
