package webserver

import (
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func (ws *WebServer) handleSSHCA(w http.ResponseWriter, r *http.Request) {
	info := tokenInfoFromContext(r.Context())
	token := extractCookie(r)

	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Redirect(w, r, "/dashboard", http.StatusFound)
		return
	}

	data := ws.baseData(r, info)
	data["MountName"] = mountName

	if pubkey, err := ws.vault.GetSSHCAPublicKey(r.Context(), mountName); err == nil {
		data["CAPublicKey"] = pubkey.PublicKey
	}

	if profiles, err := ws.vault.ListSSHCAProfiles(r.Context(), token, mountName); err == nil {
		data["Profiles"] = profiles
	}

	if certs, err := ws.vault.ListSSHCACerts(r.Context(), token, mountName); err == nil {
		for i := range certs {
			certs[i].IssuedBy = ws.resolveUser(certs[i].IssuedBy)
		}
		data["Certs"] = certs
	}

	ws.renderTemplate(w, "sshca.html", data)
}

func (ws *WebServer) handleSSHCADownload(w http.ResponseWriter, r *http.Request) {
	token := extractCookie(r)
	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	pubkey, err := ws.vault.GetSSHCAPublicKey(r.Context(), mountName)
	if err != nil || pubkey == nil {
		http.Error(w, "CA public key not found", http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "text/plain")
	w.Header().Set("Content-Disposition", "attachment; filename=ca.pub")
	_, _ = w.Write([]byte(pubkey.PublicKey)) //nolint:gosec
}

func (ws *WebServer) handleSSHCAKRLDownload(w http.ResponseWriter, r *http.Request) {
	token := extractCookie(r)
	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	krl, err := ws.vault.GetSSHCAKRL(r.Context(), mountName)
	if err != nil {
		http.Error(w, "KRL not found", http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "application/octet-stream")
	w.Header().Set("Content-Disposition", "attachment; filename=krl.bin")
	_, _ = w.Write(krl) //nolint:gosec
}

func (ws *WebServer) handleSSHCASignUser(w http.ResponseWriter, r *http.Request) {
	info := tokenInfoFromContext(r.Context())
	token := extractCookie(r)

	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
	_ = r.ParseForm()

	publicKey := strings.TrimSpace(r.FormValue("public_key"))
	if publicKey == "" {
		ws.renderSSHCAWithError(w, r, mountName, info, "Public key is required")
		return
	}

	var principals []string
	for _, line := range strings.Split(r.FormValue("principals"), "\n") {
		if v := strings.TrimSpace(line); v != "" {
			principals = append(principals, v)
		}
	}

	req := SSHCASignRequest{
		PublicKey:  publicKey,
		Principals: principals,
		Profile:   r.FormValue("profile"),
		TTL:       r.FormValue("ttl"),
	}

	result, err := ws.vault.SSHCASignUser(r.Context(), token, mountName, req)
	if err != nil {
		ws.renderSSHCAWithError(w, r, mountName, info, grpcMessage(err))
		return
	}

	ws.renderSSHCAWithResult(w, r, mountName, info, "SignUserResult", result)
}

func (ws *WebServer) handleSSHCASignHost(w http.ResponseWriter, r *http.Request) {
	info := tokenInfoFromContext(r.Context())
	token := extractCookie(r)

	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
	_ = r.ParseForm()

	publicKey := strings.TrimSpace(r.FormValue("public_key"))
	if publicKey == "" {
		ws.renderSSHCAWithError(w, r, mountName, info, "Public key is required")
		return
	}

	hostname := strings.TrimSpace(r.FormValue("hostname"))
	if hostname == "" {
		ws.renderSSHCAWithError(w, r, mountName, info, "Hostname is required")
		return
	}

	req := SSHCASignRequest{
		PublicKey:  publicKey,
		Principals: []string{hostname},
		TTL:       r.FormValue("ttl"),
	}

	result, err := ws.vault.SSHCASignHost(r.Context(), token, mountName, req)
	if err != nil {
		ws.renderSSHCAWithError(w, r, mountName, info, grpcMessage(err))
		return
	}

	ws.renderSSHCAWithResult(w, r, mountName, info, "SignHostResult", result)
}

func (ws *WebServer) handleSSHCACertDetail(w http.ResponseWriter, r *http.Request) {
	info := tokenInfoFromContext(r.Context())
	token := extractCookie(r)

	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	serial := chi.URLParam(r, "serial")
	cert, err := ws.vault.GetSSHCACert(r.Context(), token, mountName, serial)
	if err != nil {
		st, _ := status.FromError(err)
		if st.Code() == codes.NotFound {
			http.Error(w, "certificate not found", http.StatusNotFound)
			return
		}
		http.Error(w, grpcMessage(err), http.StatusInternalServerError)
		return
	}

	cert.IssuedBy = ws.resolveUser(cert.IssuedBy)
	cert.RevokedBy = ws.resolveUser(cert.RevokedBy)
	data := ws.baseData(r, info)
	data["MountName"] = mountName
	data["Cert"] = cert
	ws.renderTemplate(w, "sshca_cert_detail.html", data)
}

func (ws *WebServer) handleSSHCACertRevoke(w http.ResponseWriter, r *http.Request) {
	info := tokenInfoFromContext(r.Context())
	if !info.IsAdmin {
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}

	token := extractCookie(r)
	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	serial := chi.URLParam(r, "serial")
	if err := ws.vault.RevokeSSHCACert(r.Context(), token, mountName, serial); err != nil {
		http.Error(w, grpcMessage(err), http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, "/sshca/cert/"+serial, http.StatusSeeOther)
}

func (ws *WebServer) handleSSHCACertDelete(w http.ResponseWriter, r *http.Request) {
	info := tokenInfoFromContext(r.Context())
	if !info.IsAdmin {
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}

	token := extractCookie(r)
	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	serial := chi.URLParam(r, "serial")
	if err := ws.vault.DeleteSSHCACert(r.Context(), token, mountName, serial); err != nil {
		http.Error(w, grpcMessage(err), http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, "/sshca", http.StatusSeeOther)
}

func (ws *WebServer) handleSSHCACreateProfile(w http.ResponseWriter, r *http.Request) {
	info := tokenInfoFromContext(r.Context())
	if !info.IsAdmin {
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}

	token := extractCookie(r)
	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
	_ = r.ParseForm()

	name := strings.TrimSpace(r.FormValue("name"))
	if name == "" {
		ws.renderSSHCAWithError(w, r, mountName, info, "Profile name is required")
		return
	}

	var principals []string
	for _, line := range strings.Split(r.FormValue("allowed_principals"), "\n") {
		if v := strings.TrimSpace(line); v != "" {
			principals = append(principals, v)
		}
	}

	extensions := map[string]string{}
	for _, ext := range r.Form["extensions"] {
		extensions[ext] = ""
	}

	criticalOptions := map[string]string{}
	if v := strings.TrimSpace(r.FormValue("force_command")); v != "" {
		criticalOptions["force-command"] = v
	}
	if v := strings.TrimSpace(r.FormValue("source_address")); v != "" {
		criticalOptions["source-address"] = v
	}

	req := SSHCAProfileRequest{
		Name:              name,
		CriticalOptions:   criticalOptions,
		Extensions:        extensions,
		MaxTTL:            r.FormValue("max_ttl"),
		AllowedPrincipals: principals,
	}

	if err := ws.vault.CreateSSHCAProfile(r.Context(), token, mountName, req); err != nil {
		ws.renderSSHCAWithError(w, r, mountName, info, grpcMessage(err))
		return
	}

	http.Redirect(w, r, "/sshca/profile/"+name, http.StatusFound)
}

func (ws *WebServer) handleSSHCAProfileDetail(w http.ResponseWriter, r *http.Request) {
	info := tokenInfoFromContext(r.Context())
	token := extractCookie(r)

	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	name := chi.URLParam(r, "name")
	profile, err := ws.vault.GetSSHCAProfile(r.Context(), token, mountName, name)
	if err != nil {
		st, _ := status.FromError(err)
		if st.Code() == codes.NotFound {
			http.Error(w, "profile not found", http.StatusNotFound)
			return
		}
		http.Error(w, grpcMessage(err), http.StatusInternalServerError)
		return
	}

	data := ws.baseData(r, info)
	data["MountName"] = mountName
	data["Profile"] = profile
	ws.renderTemplate(w, "sshca_profile_detail.html", data)
}

func (ws *WebServer) handleSSHCAUpdateProfile(w http.ResponseWriter, r *http.Request) {
	info := tokenInfoFromContext(r.Context())
	if !info.IsAdmin {
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}

	token := extractCookie(r)
	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	name := chi.URLParam(r, "name")
	r.Body = http.MaxBytesReader(w, r.Body, 1<<20)
	_ = r.ParseForm()

	var principals []string
	for _, line := range strings.Split(r.FormValue("allowed_principals"), "\n") {
		if v := strings.TrimSpace(line); v != "" {
			principals = append(principals, v)
		}
	}

	extensions := map[string]string{}
	for _, ext := range r.Form["extensions"] {
		extensions[ext] = ""
	}

	criticalOptions := map[string]string{}
	if v := strings.TrimSpace(r.FormValue("force_command")); v != "" {
		criticalOptions["force-command"] = v
	}
	if v := strings.TrimSpace(r.FormValue("source_address")); v != "" {
		criticalOptions["source-address"] = v
	}

	req := SSHCAProfileRequest{
		CriticalOptions:   criticalOptions,
		Extensions:        extensions,
		MaxTTL:            r.FormValue("max_ttl"),
		AllowedPrincipals: principals,
	}

	if err := ws.vault.UpdateSSHCAProfile(r.Context(), token, mountName, name, req); err != nil {
		http.Error(w, grpcMessage(err), http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, "/sshca/profile/"+name, http.StatusSeeOther)
}

func (ws *WebServer) handleSSHCADeleteProfile(w http.ResponseWriter, r *http.Request) {
	info := tokenInfoFromContext(r.Context())
	if !info.IsAdmin {
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}

	token := extractCookie(r)
	mountName, err := ws.findSSHCAMount(r, token)
	if err != nil {
		http.Error(w, "no SSH CA engine mounted", http.StatusNotFound)
		return
	}

	name := chi.URLParam(r, "name")
	if err := ws.vault.DeleteSSHCAProfile(r.Context(), token, mountName, name); err != nil {
		http.Error(w, grpcMessage(err), http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, "/sshca", http.StatusSeeOther)
}

func (ws *WebServer) renderSSHCAWithError(w http.ResponseWriter, r *http.Request, mountName string, info *TokenInfo, errMsg string) {
	token := extractCookie(r)
	data := ws.baseData(r, info)
	data["MountName"] = mountName
	data["Error"] = errMsg

	if pubkey, err := ws.vault.GetSSHCAPublicKey(r.Context(), mountName); err == nil {
		data["CAPublicKey"] = pubkey.PublicKey
	}
	if profiles, err := ws.vault.ListSSHCAProfiles(r.Context(), token, mountName); err == nil {
		data["Profiles"] = profiles
	}
	if certs, err := ws.vault.ListSSHCACerts(r.Context(), token, mountName); err == nil {
		data["Certs"] = certs
	}

	ws.renderTemplate(w, "sshca.html", data)
}

func (ws *WebServer) renderSSHCAWithResult(w http.ResponseWriter, r *http.Request, mountName string, info *TokenInfo, resultKey string, result *SSHCASignResult) {
	token := extractCookie(r)
	data := ws.baseData(r, info)
	data["MountName"] = mountName
	data[resultKey] = result

	if pubkey, err := ws.vault.GetSSHCAPublicKey(r.Context(), mountName); err == nil {
		data["CAPublicKey"] = pubkey.PublicKey
	}
	if profiles, err := ws.vault.ListSSHCAProfiles(r.Context(), token, mountName); err == nil {
		data["Profiles"] = profiles
	}
	if certs, err := ws.vault.ListSSHCACerts(r.Context(), token, mountName); err == nil {
		data["Certs"] = certs
	}

	ws.renderTemplate(w, "sshca.html", data)
}
