package api

import (
	"context"
	"errors"
	"fmt"
	"html"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/mrhid6/vantage/sitesvc/internal/store"
)

const (
	minPasswordLength = 12
	signupPerIPLimit  = 3
	signupPerIPWindow = time.Hour
)

type signupBody struct {
	InstanceName string `json:"instance_name"`
	Email        string `json:"email"`
	Password     string `json:"password"`
	Website      string `json:"website"`
}

func (s *Server) handleSignup(w http.ResponseWriter, r *http.Request) {
	var body signupBody
	if !decode(w, r, &body) {
		return
	}

	if strings.TrimSpace(body.Website) != "" {
		writeJSON(w, http.StatusAccepted, map[string]string{"status": "check_email"})
		return
	}

	var problems []fieldError

	instanceName, nameErr := text("instance_name", body.InstanceName, true, maxShort)
	if nameErr != nil {
		problems = append(problems, *nameErr)
	}

	addr, emailErr := email("email", body.Email)
	if emailErr != nil {
		problems = append(problems, *emailErr)
	}

	if utf8.RuneCountInString(body.Password) < minPasswordLength {
		problems = append(problems, fieldError{
			Field:   "password",
			Message: fmt.Sprintf("Use at least %d characters.", minPasswordLength),
		})
	}

	if len(problems) > 0 {
		writeFieldErrors(w, problems)
		return
	}

	if !s.signups.allow(s.clientIP(r)) {
		w.Header().Set("Retry-After", fmt.Sprintf("%d", int(signupPerIPWindow.Seconds())))
		writeJSON(w, http.StatusTooManyRequests, map[string]string{
			"error": "Too many organisations created from here recently. Try again later.",
		})
		return
	}

	if !s.mail.Enabled() {
		log.Println("signup refused: smtp is not configured, so no verification email could be sent")
		writeJSON(w, http.StatusServiceUnavailable, map[string]string{
			"error": "Signup is unavailable right now. Email support@hostxtra.co.uk and we will set you up.",
		})
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
	defer cancel()

	token, err := store.CreatePending(ctx, instanceName, addr, body.Password)
	switch {
	case errors.Is(err, store.ErrEmailTaken):
		writeJSON(w, http.StatusConflict, map[string]any{
			"error": "That email already has an account.",
			"fields": []fieldError{{
				Field:   "email",
				Message: "This address is already registered. Sign in instead.",
			}},
		})
		return
	case errors.Is(err, store.ErrNameRejected):
		writeFieldErrors(w, []fieldError{{
			Field:   "instance_name",
			Message: strings.TrimPrefix(err.Error(), store.ErrNameRejected.Error()+": "),
		}})
		return
	case err != nil:
		log.Printf("signup: create pending: %v", err)
		writeJSON(w, http.StatusInternalServerError, map[string]string{
			"error": "We could not start that signup. Try again in a moment.",
		})
		return
	}

	link := s.verifyURL(token)
	if err := s.mail.SendVerification(addr, instanceName, link, store.PendingTTL); err != nil {

		log.Printf("signup: send verification to %s: %v", addr, err)
		writeJSON(w, http.StatusBadGateway, map[string]string{
			"error": "We could not send the confirmation email. Check the address, or email support@hostxtra.co.uk.",
		})
		return
	}

	writeJSON(w, http.StatusAccepted, map[string]string{"status": "check_email"})
}

func (s *Server) verifyURL(token string) string {
	base := strings.TrimSuffix(s.publicURL, "/")
	return fmt.Sprintf("%s/api/verify?token=%s", base, url.QueryEscape(token))
}

func (s *Server) handleVerify(w http.ResponseWriter, r *http.Request) {
	token := r.URL.Query().Get("token")
	if token == "" {
		s.verifyPage(w, http.StatusBadRequest, "Link incomplete",
			"That link is missing its token. Copy the whole address from the email and try again.")
		return
	}

	ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second)
	defer cancel()

	inst, err := store.Verify(ctx, token)
	switch {
	case errors.Is(err, store.ErrBadToken):
		s.verifyPage(w, http.StatusGone, "Link expired",
			"This link has already been used or has expired. Start the signup again and we will send a new one.")
		return
	case errors.Is(err, store.ErrEmailTaken):
		s.verifyPage(w, http.StatusConflict, "Already registered",
			"That address already has an account. Sign in instead.")
		return
	case errors.Is(err, store.ErrNameRejected):
		s.verifyPage(w, http.StatusUnprocessableEntity, "Name unavailable",
			"We could not use that organisation name. Start the signup again with a different one.")
		return
	case err != nil:
		log.Printf("verify: %v", err)
		s.verifyPage(w, http.StatusInternalServerError, "Something went wrong",
			"We could not finish creating your organisation. Email support@hostxtra.co.uk and we will sort it out.")
		return
	}

	log.Printf("verify: provisioned instance %s (%s)", inst.Slug, inst.InstanceID)

	if login := s.loginURL(inst.Slug); login != "" {
		http.Redirect(w, r, login, http.StatusSeeOther)
		return
	}

	s.verifyPage(w, http.StatusOK, "Organisation ready",
		fmt.Sprintf("%s is set up and you are its owner. You can sign in now.", inst.Name))
}

// loginURL is the instance-specific sign-in URL a verified owner is sent to. Each
// instance lives on its own subdomain (<slug>.vantage.hostxtra.co.uk), so the slug
// must be substituted per signup rather than pointing at one shared address.
//
// APP_LOGIN_URL is a template. A "{slug}" placeholder is replaced with the
// instance's slug; a value without one is treated as a literal (a single shared
// login page) so a plain URL still works. An empty value falls back to the
// confirmation page.
func (s *Server) loginURL(slug string) string {
	if s.appLoginURL == "" {
		return ""
	}
	return strings.ReplaceAll(s.appLoginURL, "{slug}", url.PathEscape(slug))
}

func (s *Server) verifyPage(w http.ResponseWriter, status int, heading, detail string) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("X-Content-Type-Options", "nosniff")
	w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
	w.WriteHeader(status)

	page := fmt.Sprintf(`<!doctype html>
<html lang="en-GB">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<title>%s Vantage</title>
<style>
  :root { color-scheme: light dark; }
  body {
    margin: 0; min-height: 100vh; display: grid; place-items: center;
    background: #eaedf3; color: #0a1b33; padding: 2rem;
    font: 16px/1.6 ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  }
  main { max-width: 46ch; }
  h1 { font-size: 1.6rem; letter-spacing: -0.03em; margin: 0 0 0.6rem; }
  p { margin: 0; color: #41556f; }
  a { color: #0b2a58; }
  @media (prefers-color-scheme: dark) {
    body { background: #071628; color: #e4ecf6; }
    p { color: #9fb3ca; }
    a { color: #5b9be8; }
  }
</style>
</head>
<body>
  <main>
    <h1>%s</h1>
    <p>%s</p>
  </main>
</body>
</html>`, html.EscapeString(heading), html.EscapeString(heading), html.EscapeString(detail))

	if _, err := w.Write([]byte(page)); err != nil {
		log.Printf("write verify page: %v", err)
	}
}

func writeFieldErrors(w http.ResponseWriter, problems []fieldError) {
	writeJSON(w, http.StatusUnprocessableEntity, map[string]any{
		"error":  "Some fields need another look.",
		"fields": problems,
	})
}
