package identity

import (
	"log/slog"
	"net/http"
	"strings"

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

	qmiddleware "qycw/go-server/internal/middleware"
	"qycw/go-server/internal/response"
)

type OAuthHandler struct {
	service     *Service
	auth        *qmiddleware.Auth
	frontendURL string
	logger      *slog.Logger
}

func NewOAuthHandler(service *Service, auth *qmiddleware.Auth, frontendURL string, logger *slog.Logger) *OAuthHandler {
	return &OAuthHandler{service: service, auth: auth, frontendURL: frontendURL, logger: logger}
}

func (h *OAuthHandler) Routes() http.Handler {
	r := chi.NewRouter()
	r.Get("/providers", h.providers)
	r.Get("/{plugin_id}/url", h.getURL)
	r.Get("/{plugin_id}/callback", h.callback)
	r.With(h.auth.RequireUser).Post("/{plugin_id}/bind", h.bind)
	r.With(h.auth.RequireUser).Delete("/{plugin_id}/unbind", h.unbind)
	return r
}

func (h *OAuthHandler) providers(w http.ResponseWriter, r *http.Request) {
	list, _ := h.service.ListAuthProviders(r.Context())
	response.OK(w, list)
}

func (h *OAuthHandler) getURL(w http.ResponseWriter, r *http.Request) {
	pluginID := chi.URLParam(r, "plugin_id")
	redirectURI := r.URL.Query().Get("redirect_uri")
	result, err := h.service.GetOAuthURL(r.Context(), pluginID, redirectURI)
	if err != nil {
		writeOAuthError(w, err)
		return
	}
	response.OK(w, result)
}

func (h *OAuthHandler) callback(w http.ResponseWriter, r *http.Request) {
	pluginID := chi.URLParam(r, "plugin_id")
	code := r.URL.Query().Get("code")
	state := r.URL.Query().Get("state")
	redirectURI := r.URL.Query().Get("redirect_uri")

	profile, err := h.service.ExchangeOAuthCode(r.Context(), pluginID, code, state, redirectURI)
	if err != nil {
		h.logger.Warn("oauth exchange failed", "plugin", pluginID, "error", err)
		http.Redirect(w, r, strings.TrimRight(h.frontendURL, "/")+"/login?oauth_error=1", http.StatusFound)
		return
	}
	user, err := h.service.FindOrCreateOAuthUser(r.Context(), pluginID, profile)
	if err != nil {
		h.logger.Error("oauth find/create user failed", "plugin", pluginID, "error", err)
		http.Redirect(w, r, strings.TrimRight(h.frontendURL, "/")+"/login?oauth_error=1", http.StatusFound)
		return
	}
	tokens, err := h.service.IssueOAuthTokenPair(r.Context(), user)
	if err != nil {
		h.logger.Error("oauth issue token failed", "error", err)
		http.Redirect(w, r, strings.TrimRight(h.frontendURL, "/")+"/login?oauth_error=1", http.StatusFound)
		return
	}
	dest := strings.TrimRight(h.frontendURL, "/") + "/oauth/callback" +
		"#access_token=" + tokens["access_token"].(string) +
		"&refresh_token=" + tokens["refresh_token"].(string)
	http.Redirect(w, r, dest, http.StatusFound)
}

func (h *OAuthHandler) bind(w http.ResponseWriter, r *http.Request) {
	pluginID := chi.URLParam(r, "plugin_id")
	principal, _ := qmiddleware.PrincipalFromContext(r.Context())
	code := r.URL.Query().Get("code")
	state := r.URL.Query().Get("state")
	redirectURI := r.URL.Query().Get("redirect_uri")

	profile, err := h.service.ExchangeOAuthCode(r.Context(), pluginID, code, state, redirectURI)
	if err != nil {
		writeOAuthError(w, err)
		return
	}
	if err := h.service.BindOAuth(r.Context(), principal.ID, pluginID, profile); err != nil {
		writeOAuthError(w, err)
		return
	}
	response.OK(w, map[string]any{"ok": true})
}

func (h *OAuthHandler) unbind(w http.ResponseWriter, r *http.Request) {
	pluginID := chi.URLParam(r, "plugin_id")
	principal, _ := qmiddleware.PrincipalFromContext(r.Context())
	if err := h.service.UnbindOAuth(r.Context(), principal.ID, pluginID); err != nil {
		writeOAuthError(w, err)
		return
	}
	response.OK(w, map[string]any{"ok": true})
}

func writeOAuthError(w http.ResponseWriter, err error) {
	if e, ok := err.(HTTPError); ok {
		response.Detail(w, e.Status, e.Detail)
		return
	}
	response.Error(w, http.StatusInternalServerError, "内部错误")
}
