package mail

import (
	"errors"
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"

	"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
	"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
	"github.com/ultisuite/ulti-backend/internal/api/middleware"
	"github.com/ultisuite/ulti-backend/internal/mail/connect"
	mailoauth "github.com/ultisuite/ulti-backend/internal/mail/oauth"
)

func (h *Handler) TestAccountConnection(w http.ResponseWriter, r *http.Request) {
	h.testAccountConnection(w, r, "")
}

func (h *Handler) TestStoredAccountConnection(w http.ResponseWriter, r *http.Request) {
	accountID := chi.URLParam(r, "accountID")
	if d := validateAccountUUID(accountID); d != nil {
		apivalidate.WriteNotFound(w, r, "not found")
		return
	}
	h.testAccountConnection(w, r, accountID)
}

func (h *Handler) testAccountConnection(w http.ResponseWriter, r *http.Request, accountIDFromURL string) {
	claims := middleware.ClaimsFromContext(r.Context())

	var req testAccountRequest
	if err := apivalidate.DecodeJSON(w, r, maxAccountRequestBody, &req); err != nil {
		return
	}
	if accountIDFromURL != "" {
		req.AccountID = accountIDFromURL
	}

	if verr := validateTestAccount(&req); verr != nil {
		apivalidate.WriteValidationError(w, r, verr)
		return
	}

	cred, err := h.svc.CredentialForConnectionTest(r.Context(), claims.Sub, &req)
	if err != nil {
		if errors.Is(err, ErrNotFound) {
			apivalidate.WriteNotFound(w, r, "not found")
			return
		}
		if errors.Is(err, ErrInvalidAccountCredentials) {
			apiresponse.WriteError(w, r, http.StatusBadRequest, apiresponse.CodeInvalidRequest, "missing credentials for connection test", nil)
			return
		}
		if errors.Is(err, ErrCredentialsUnavailable) {
			apiresponse.WriteError(w, r, http.StatusInternalServerError, apiresponse.CodeInternal, "credentials encryption unavailable", nil)
			return
		}
		h.logger.Error("resolve test credentials", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}

	result := connect.Test(r.Context(), connect.ServerConfig{
		IMAPHost: req.IMAPHost,
		IMAPPort: req.IMAPPort,
		IMAPTLS:  req.IMAPTLS,
		SMTPHost: req.SMTPHost,
		SMTPPort: req.SMTPPort,
		SMTPTLS:  req.SMTPTLS,
	}, cred)

	if result.IMAPError != "" {
		result.IMAPError = connect.SanitizeError(result.IMAPError)
	}
	if result.SMTPError != "" {
		result.SMTPError = connect.SanitizeError(result.SMTPError)
	}

	apiresponse.WriteJSON(w, http.StatusOK, result)
}

func (h *Handler) ListOAuthProviders(w http.ResponseWriter, r *http.Request) {
	if h.oauth == nil {
		apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"providers": []string{}})
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{
		"providers": h.oauth.EnabledProviders(),
	})
}

func (h *Handler) StartOAuthAccount(w http.ResponseWriter, r *http.Request) {
	if h.oauth == nil {
		apiresponse.WriteError(w, r, http.StatusServiceUnavailable, apiresponse.CodeInternal, "mail oauth unavailable", nil)
		return
	}
	claims := middleware.ClaimsFromContext(r.Context())

	var req oauthStartRequest
	if err := apivalidate.DecodeJSON(w, r, maxAccountRequestBody, &req); err != nil {
		return
	}
	if verr := validateOAuthStart(&req); verr != nil {
		apivalidate.WriteValidationError(w, r, verr)
		return
	}

	pending := h.oauthPendingFromRequest(&req)
	authURL, state, err := h.oauth.Start(r.Context(), claims.Sub, mailoauth.Provider(req.Provider), pending)
	if err != nil {
		h.logger.Error("oauth start", "error", err, "provider", req.Provider)
		apiresponse.WriteError(w, r, http.StatusBadRequest, apiresponse.CodeInvalidRequest, err.Error(), nil)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]string{
		"authorization_url": authURL,
		"state":             state,
	})
}

func (h *Handler) OAuthCallback(w http.ResponseWriter, r *http.Request) {
	if h.oauth == nil || h.svc == nil {
		http.Redirect(w, r, h.oauthReturnURL("error", "oauth_unavailable"), http.StatusFound)
		return
	}

	state := strings.TrimSpace(r.URL.Query().Get("state"))
	code := strings.TrimSpace(r.URL.Query().Get("code"))
	if errParam := r.URL.Query().Get("error"); errParam != "" {
		http.Redirect(w, r, h.oauthReturnURL("error", errParam), http.StatusFound)
		return
	}
	if state == "" || code == "" {
		http.Redirect(w, r, h.oauthReturnURL("error", "missing_code"), http.StatusFound)
		return
	}

	pending, token, err := h.oauth.Exchange(r.Context(), state, code)
	if err != nil {
		h.logger.Error("oauth exchange", "error", err)
		http.Redirect(w, r, h.oauthReturnURL("error", "exchange_failed"), http.StatusFound)
		return
	}

	cred := mailoauth.CredentialFromToken(pending.Email, pending.Provider, token)
	_, err = h.svc.CreateAccountWithCredential(r.Context(), pending.UserExternalID, &createAccountRequest{
		Name:     pending.Name,
		Email:    pending.Email,
		Provider: pending.ProviderID,
		IMAPHost: pending.IMAPHost,
		IMAPPort: pending.IMAPPort,
		IMAPTLS:  pending.IMAPTLS,
		SMTPHost: pending.SMTPHost,
		SMTPPort: pending.SMTPPort,
		SMTPTLS:  pending.SMTPTLS,
	}, cred)
	if err != nil {
		h.logger.Error("oauth create account", "error", err)
		http.Redirect(w, r, h.oauthReturnURL("error", "create_failed"), http.StatusFound)
		return
	}

	http.Redirect(w, r, h.oauthReturnURL("success", ""), http.StatusFound)
}

func (h *Handler) oauthPendingFromRequest(req *oauthStartRequest) mailoauth.PendingAccount {
	name := req.Name
	if name == "" {
		name = req.Email
	}
	return mailoauth.PendingAccount{
		Email:      req.Email,
		Name:       name,
		ProviderID: req.ProviderID,
		IMAPHost:   req.IMAPHost,
		IMAPPort:   req.IMAPPort,
		IMAPTLS:    req.IMAPTLS,
		SMTPHost:   req.SMTPHost,
		SMTPPort:   req.SMTPPort,
		SMTPTLS:    req.SMTPTLS,
	}
}

func (h *Handler) oauthReturnURL(status, code string) string {
	base := strings.TrimRight(h.appURL, "/")
	if base == "" {
		base = "http://localhost:3000"
	}
	u := base + "/mail/settings/accounts?oauth=" + status
	if code != "" {
		u += "&code=" + code
	}
	return u
}
