package identity

import (
	"encoding/base64"
	"encoding/json"
	"errors"
	"io"
	"log/slog"
	"net/http"
	"strconv"
	"strings"

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

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

type Handler struct {
	service *Service
	auth    *qmiddleware.Auth
	logger  *slog.Logger
}

func NewHandler(service *Service, logger *slog.Logger) *Handler {
	return &Handler{
		service: service,
		auth:    qmiddleware.NewAuth(service, logger),
		logger:  logger,
	}
}

func (h *Handler) Routes() http.Handler {
	r := chi.NewRouter()

	r.Route("/auth", func(r chi.Router) {
		r.Post("/send-code", h.sendRegisterCode)
		r.Post("/send-login-code", h.sendLoginCode)
		r.Post("/send-sms-code", h.sendRegisterSMSCode)
		r.Post("/send-login-sms-code", h.sendLoginSMSCode)
		r.Post("/register", h.register)
		r.Post("/register-by-sms", h.registerBySMS)
		r.Post("/login", h.login)
		r.Post("/login-by-code", h.loginByCode)
		r.Post("/login-by-sms", h.loginBySMS)
		r.Post("/refresh", h.refresh)
		r.Post("/logout", h.logout)
	})

	r.Route("/admin/auth", func(r chi.Router) {
		r.Post("/login", h.adminLogin)
		r.Post("/refresh", h.refresh)
		r.Post("/logout", h.logout)
		r.With(h.auth.RequireRole("admin", "super_admin")).Get("/me", h.adminMe)
	})

	r.Route("/users", func(r chi.Router) {
		r.Use(h.auth.RequireUser)
		r.Get("/me", h.me)
		r.Patch("/me", h.updateMe)
		r.Put("/me/password", h.changePassword)
		r.Post("/me/send-bind-email-code", h.sendBindEmailCode)
		r.Post("/me/send-bind-phone-code", h.sendBindPhoneCode)
		r.Post("/me/bind-email", h.bindEmail)
		r.Post("/me/bind-phone", h.bindPhone)
		r.Get("/me/oauth-connections", func(w http.ResponseWriter, r *http.Request) {
			principal, _ := qmiddleware.PrincipalFromContext(r.Context())
			list, err := h.service.ListOAuthConnections(r.Context(), principal.ID)
			if err != nil {
				response.Error(w, http.StatusInternalServerError, err.Error())
				return
			}
			response.OK(w, list)
		})
	})

	r.Route("/me", func(r chi.Router) {
		r.Use(h.auth.RequireUser)
		r.Get("/verification-status", h.getVerificationStatus)
		r.Get("/verification-options", h.getVerificationOptions)
		r.Post("/verification/two-factor", h.submitTwoFactorVerification)
		r.Post("/verification/three-factor", h.submitThreeFactorVerification)
		r.Post("/verification/face-api", h.submitFaceAPIVerification)
		r.Post("/verification/redirect-session", h.createRedirectVerificationSession)
		r.Post("/verification/redirect-result", h.queryRedirectVerificationResult)
		r.Post("/verification/quota/purchase", h.purchaseVerificationQuota)
	})

	r.Route("/verification", func(r chi.Router) {
		r.Get("/notify/{provider_name}", h.verificationNotify)
		r.Post("/notify/{provider_name}", h.verificationNotify)
	})

	r.Route("/admin", func(r chi.Router) {
		r.Use(h.auth.RequireRole("admin", "super_admin"))
		r.Get("/verification-records", h.listVerificationRecords)
		r.Post("/users/{user_id}/verification", h.manualVerifyUser)
	})

	return r
}

func (h *Handler) AdminAuthRoutes() http.Handler {
	r := chi.NewRouter()
	r.Post("/login", h.adminLogin)
	r.Post("/refresh", h.refresh)
	r.Post("/logout", h.logout)
	r.With(h.auth.RequireRole("admin", "super_admin")).Get("/me", h.adminMe)
	return r
}

func (h *Handler) sendRegisterCode(w http.ResponseWriter, r *http.Request) {
	var req SendCodeRequest
	if !decode(w, r, &req) {
		return
	}
	cooldown, err := h.service.SendRegisterCode(r.Context(), req)
	h.writeResult(w, map[string]int{"cooldown": cooldown}, err)
}

func (h *Handler) sendLoginCode(w http.ResponseWriter, r *http.Request) {
	var req SendCodeRequest
	if !decode(w, r, &req) {
		return
	}
	cooldown, err := h.service.SendLoginCode(r.Context(), req)
	h.writeResult(w, map[string]int{"cooldown": cooldown}, err)
}

func (h *Handler) sendRegisterSMSCode(w http.ResponseWriter, r *http.Request) {
	var req SendSMSRequest
	if !decode(w, r, &req) {
		return
	}
	cooldown, err := h.service.SendRegisterSMSCode(r.Context(), req)
	h.writeResult(w, map[string]int{"cooldown": cooldown}, err)
}

func (h *Handler) sendLoginSMSCode(w http.ResponseWriter, r *http.Request) {
	var req SendSMSRequest
	if !decode(w, r, &req) {
		return
	}
	cooldown, err := h.service.SendLoginSMSCode(r.Context(), req)
	h.writeResult(w, map[string]int{"cooldown": cooldown}, err)
}

func (h *Handler) register(w http.ResponseWriter, r *http.Request) {
	var req RegisterRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.Register(r.Context(), req)
	h.writeResult(w, data, err)
}

func (h *Handler) registerBySMS(w http.ResponseWriter, r *http.Request) {
	var req RegisterBySMSRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.RegisterBySMS(r.Context(), req)
	h.writeResult(w, data, err)
}

func (h *Handler) login(w http.ResponseWriter, r *http.Request) {
	var req LoginRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.Login(r.Context(), req, false)
	h.writeResult(w, data, err)
}

func (h *Handler) adminLogin(w http.ResponseWriter, r *http.Request) {
	var req LoginRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.Login(r.Context(), req, true)
	h.writeResult(w, data, err)
}

func (h *Handler) loginByCode(w http.ResponseWriter, r *http.Request) {
	var req LoginByCodeRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.LoginByCode(r.Context(), req)
	h.writeResult(w, data, err)
}

func (h *Handler) loginBySMS(w http.ResponseWriter, r *http.Request) {
	var req LoginBySMSRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.LoginBySMS(r.Context(), req)
	h.writeResult(w, data, err)
}

func (h *Handler) refresh(w http.ResponseWriter, r *http.Request) {
	var req RefreshRequest
	if !decode(w, r, &req) {
		return
	}
	tokens, err := h.service.Refresh(r.Context(), req.RefreshToken)
	h.writeResult(w, tokens, err)
}

func (h *Handler) logout(w http.ResponseWriter, r *http.Request) {
	var req RefreshRequest
	if !decode(w, r, &req) {
		return
	}
	err := h.service.Logout(r.Context(), req.RefreshToken)
	h.writeResult(w, nil, err)
}

func (h *Handler) me(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	response.OK(w, h.service.UserResponse(user))
}

func (h *Handler) adminMe(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	response.OK(w, h.service.UserResponse(user))
}

func (h *Handler) updateMe(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req UserUpdateRequest
	if !decode(w, r, &req) {
		return
	}
	updated, err := h.service.UpdateMe(r.Context(), user, req)
	h.writeResult(w, h.service.UserResponse(updated), err)
}

func (h *Handler) changePassword(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req PasswordChangeRequest
	if !decode(w, r, &req) {
		return
	}
	updated, err := h.service.ChangePassword(r.Context(), user, req)
	h.writeResult(w, h.service.UserResponse(updated), err)
}

func (h *Handler) sendBindEmailCode(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req SendCodeRequest
	if !decode(w, r, &req) {
		return
	}
	cooldown, err := h.service.SendBindEmailCode(r.Context(), user, req)
	h.writeResult(w, map[string]int{"cooldown": cooldown}, err)
}

func (h *Handler) sendBindPhoneCode(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req SendSMSRequest
	if !decode(w, r, &req) {
		return
	}
	cooldown, err := h.service.SendBindSMSCode(r.Context(), user, req)
	h.writeResult(w, map[string]int{"cooldown": cooldown}, err)
}

func (h *Handler) bindEmail(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req BindEmailRequest
	if !decode(w, r, &req) {
		return
	}
	updated, err := h.service.BindEmail(r.Context(), user, req)
	h.writeResult(w, h.service.UserResponse(updated), err)
}

func (h *Handler) bindPhone(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req BindPhoneRequest
	if !decode(w, r, &req) {
		return
	}
	updated, err := h.service.BindPhone(r.Context(), user, req)
	h.writeResult(w, h.service.UserResponse(updated), err)
}

func (h *Handler) getVerificationStatus(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	data, err := h.service.GetVerificationStatus(r.Context(), user)
	h.writeResult(w, data, err)
}

func (h *Handler) getVerificationOptions(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.GetVerificationOptions(r.Context())
	h.writeResult(w, data, err)
}

func (h *Handler) submitTwoFactorVerification(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req VerificationIdentityRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.SubmitTwoFactorVerification(r.Context(), user, req)
	h.writeResult(w, data, err)
}

func (h *Handler) submitThreeFactorVerification(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req VerificationThreeFactorRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.SubmitThreeFactorVerification(r.Context(), user, req)
	h.writeResult(w, data, err)
}

func (h *Handler) submitFaceAPIVerification(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req VerificationIdentityRequest
	imageBase64 := ""
	if err := r.ParseMultipartForm(4 << 20); err == nil {
		req.RealName = r.FormValue("real_name")
		req.IDCardNo = r.FormValue("id_card_no")
		for _, field := range []string{"image", "face_image", "file"} {
			file, _, err := r.FormFile(field)
			if err != nil {
				continue
			}
			raw, _ := io.ReadAll(io.LimitReader(file, 2<<20))
			_ = file.Close()
			imageBase64 = base64.StdEncoding.EncodeToString(raw)
			break
		}
	} else if !decode(w, r, &req) {
		return
	}
	data, err := h.service.SubmitFaceAPIVerification(r.Context(), user, req, imageBase64)
	h.writeResult(w, data, err)
}

func (h *Handler) createRedirectVerificationSession(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req VerificationRedirectRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.CreateRedirectVerificationSession(r.Context(), user, req)
	h.writeResult(w, data, err)
}

func (h *Handler) queryRedirectVerificationResult(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req VerificationRedirectResultRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.QueryRedirectVerificationResult(r.Context(), user, req)
	h.writeResult(w, data, err)
}

func (h *Handler) verificationNotify(w http.ResponseWriter, r *http.Request) {
	payload := map[string]any{}
	for key, values := range r.URL.Query() {
		if len(values) > 0 {
			payload[key] = values[0]
		}
	}
	if r.Method == http.MethodPost {
		contentType := r.Header.Get("Content-Type")
		if strings.Contains(contentType, "application/json") {
			_ = json.NewDecoder(r.Body).Decode(&payload)
		} else {
			_ = r.ParseForm()
			for key, values := range r.PostForm {
				if len(values) > 0 {
					payload[key] = values[0]
				}
			}
		}
	}
	data, err := h.service.HandleRedirectVerificationNotify(r.Context(), chi.URLParam(r, "provider_name"), payload)
	h.writeResult(w, data, err)
}

func (h *Handler) purchaseVerificationQuota(w http.ResponseWriter, r *http.Request) {
	user, ok := h.currentUser(w, r)
	if !ok {
		return
	}
	var req VerificationQuotaPurchaseRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.PurchaseVerificationQuota(r.Context(), user, req.Count)
	h.writeResult(w, data, err)
}

func (h *Handler) listVerificationRecords(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	page, _ := strconv.Atoi(query.Get("page"))
	perPage, _ := strconv.Atoi(query.Get("per_page"))
	userID, _ := strconv.ParseInt(query.Get("user_id"), 10, 64)
	data, err := h.service.ListVerificationRecords(r.Context(), query.Get("status"), userID, page, perPage)
	h.writeResult(w, data, err)
}

func (h *Handler) manualVerifyUser(w http.ResponseWriter, r *http.Request) {
	userID, _ := strconv.ParseInt(chi.URLParam(r, "user_id"), 10, 64)
	var req AdminManualVerifyRequest
	if !decode(w, r, &req) {
		return
	}
	data, err := h.service.ManualVerifyUser(r.Context(), userID, req)
	h.writeResult(w, data, err)
}

func (h *Handler) currentUser(w http.ResponseWriter, r *http.Request) (User, bool) {
	principal, ok := qmiddleware.PrincipalFromContext(r.Context())
	if !ok {
		response.Detail(w, http.StatusUnauthorized, "Could not validate credentials")
		return User{}, false
	}
	user, ok := principal.User.(User)
	if !ok {
		response.Detail(w, http.StatusUnauthorized, "Could not validate credentials")
		return User{}, false
	}
	return user, true
}

func (h *Handler) writeResult(w http.ResponseWriter, data any, err error) {
	if err != nil {
		h.writeError(w, err)
		return
	}
	response.OK(w, data)
}

func (h *Handler) writeError(w http.ResponseWriter, err error) {
	var httpErr HTTPError
	if errors.As(err, &httpErr) {
		response.Detail(w, httpErr.Status, httpErr.Detail)
		return
	}
	h.logger.Error("identity handler error", "error", err)
	response.Detail(w, http.StatusInternalServerError, msgServiceError)
}

func decode(w http.ResponseWriter, r *http.Request, out any) bool {
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(out); err != nil {
		response.Detail(w, http.StatusBadRequest, msgInvalidParams)
		return false
	}
	return true
}
