package commerce

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

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

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

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

func NewTicketHandler(service *Service, auth *qmiddleware.Auth, logger *slog.Logger) *TicketHandler {
	return &TicketHandler{service: service, auth: auth, logger: logger}
}

func (h *TicketHandler) Routes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireUser)
	r.Get("/departments", h.departments)
	r.Get("/instances", h.instances)
	r.Post("/attachments/images", h.uploadImage("user"))
	r.Get("/attachments/images/thumbnail", h.serveLegacyImage(false, true))
	r.Head("/attachments/images/thumbnail", h.serveLegacyImage(false, true))
	r.Get("/attachments/images/file/{token}", h.serveImage(false, false))
	r.Get("/attachments/images/thumbnail/{token}", h.serveImage(false, true))
	r.Head("/attachments/images/file/{token}", h.serveImage(false, false))
	r.Head("/attachments/images/thumbnail/{token}", h.serveImage(false, true))
	r.Get("/", h.list)
	r.Post("/", h.create)
	r.Get("/{ticket_id}", h.detail)
	r.Post("/{ticket_id}/messages", h.reply)
	r.Post("/{ticket_id}/close", h.close)
	r.Post("/{ticket_id}/rating", h.rating)
	r.Post("/{ticket_id}/reopen", h.reopen)
	return r
}

func (h *TicketHandler) AdminRoutes() http.Handler {
	r := chi.NewRouter()
	r.Use(h.auth.RequireRole("admin", "super_admin"))
	r.Post("/attachments/images", h.uploadImage("admin"))
	r.Get("/attachments/images/thumbnail", h.serveLegacyImage(true, true))
	r.Head("/attachments/images/thumbnail", h.serveLegacyImage(true, true))
	r.Get("/attachments/images/file/{token}", h.serveImage(true, false))
	r.Get("/attachments/images/thumbnail/{token}", h.serveImage(true, true))
	r.Head("/attachments/images/file/{token}", h.serveImage(true, false))
	r.Head("/attachments/images/thumbnail/{token}", h.serveImage(true, true))
	r.Get("/departments", h.adminDepartments)
	r.Post("/departments", h.adminSaveDepartment)
	r.Patch("/departments/{department_id}", h.adminSaveDepartment)
	r.Delete("/departments/{department_id}", h.adminDeleteDepartment)
	r.Get("/staff", h.adminStaff)
	r.Get("/stats", h.adminStats)
	r.Get("/reply-templates", h.listReplyTemplates)
	r.Post("/reply-templates", h.createReplyTemplate)
	r.Patch("/reply-templates/{template_id}", h.updateReplyTemplate)
	r.Delete("/reply-templates/{template_id}", h.deleteReplyTemplate)
	r.Get("/", h.adminList)
	r.Get("/{ticket_id}", h.adminDetail)
	r.Patch("/{ticket_id}", h.adminUpdate)
	r.Post("/{ticket_id}/messages", h.adminReply)
	r.Post("/{ticket_id}/template-replies", h.adminTemplateReply)
	r.Post("/{ticket_id}/close", h.adminClose)
	r.Post("/{ticket_id}/reopen", h.adminReopen)
	return r
}

func (h *TicketHandler) departments(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.ListTicketDepartments(r.Context())
	h.writeResult(w, data, err)
}

func (h *TicketHandler) instances(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	data, err := h.service.ListTicketInstances(r.Context(), userID)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) list(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	limit := queryInt(r.URL.Query().Get("limit"), 50, 1, 100)
	offset := queryInt(r.URL.Query().Get("offset"), 0, 0, 100000)
	status := strings.TrimSpace(r.URL.Query().Get("status"))
	data, err := h.service.ListTickets(r.Context(), userID, status, limit, offset)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) create(w http.ResponseWriter, r *http.Request) {
	userID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	var req TicketCreateRequest
	if !decodeTicketJSON(w, r, &req) {
		return
	}
	data, err := h.service.CreateTicket(r.Context(), userID, req)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) detail(w http.ResponseWriter, r *http.Request) {
	userID, ticketID, ok := notificationUserAndID(w, r, "ticket_id")
	if !ok {
		return
	}
	data, err := h.service.GetTicket(r.Context(), userID, ticketID)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) reply(w http.ResponseWriter, r *http.Request) {
	userID, ticketID, ok := notificationUserAndID(w, r, "ticket_id")
	if !ok {
		return
	}
	var req TicketReplyRequest
	if !decodeTicketJSON(w, r, &req) {
		return
	}
	data, err := h.service.ReplyTicket(r.Context(), userID, ticketID, req)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) close(w http.ResponseWriter, r *http.Request) {
	userID, ticketID, ok := notificationUserAndID(w, r, "ticket_id")
	if !ok {
		return
	}
	data, err := h.service.CloseTicket(r.Context(), userID, ticketID)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) rating(w http.ResponseWriter, r *http.Request) {
	userID, ticketID, ok := notificationUserAndID(w, r, "ticket_id")
	if !ok {
		return
	}
	var req TicketRatingRequest
	if !decodeTicketJSON(w, r, &req) {
		return
	}
	data, err := h.service.RateTicket(r.Context(), userID, ticketID, req)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) reopen(w http.ResponseWriter, r *http.Request) {
	userID, ticketID, ok := notificationUserAndID(w, r, "ticket_id")
	if !ok {
		return
	}
	data, err := h.service.ReopenTicket(r.Context(), userID, ticketID)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) uploadImage(actorType string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userID, ok := notificationUserID(w, r)
		if !ok {
			return
		}
		if err := r.ParseMultipartForm(maxTicketImageBytes + 1024); err != nil {
			response.Detail(w, http.StatusBadRequest, msgInvalidParams)
			return
		}
		file, header, err := r.FormFile("file")
		if err != nil {
			file, header, err = r.FormFile("image")
		}
		if err != nil {
			response.Detail(w, http.StatusBadRequest, "missing upload file")
			return
		}
		data, err := h.service.UploadTicketImage(r.Context(), actorType, userID, file, header)
		h.writeResult(w, data, err)
	}
}

func (h *TicketHandler) serveImage(admin bool, thumbnail bool) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userID, ok := notificationUserID(w, r)
		if !ok {
			return
		}
		data, err := h.service.TicketAttachmentFile(r.Context(), userID, chi.URLParam(r, "token"), admin, thumbnail)
		if err != nil {
			h.writeError(w, err)
			return
		}
		w.Header().Set("Content-Type", data.ContentType)
		w.Header().Set("Cache-Control", "private, max-age=3600")
		w.Header().Set("Content-Disposition", `inline; filename="`+strings.ReplaceAll(data.Name, `"`, "")+`"`)
		http.ServeFile(w, r, data.Path)
	}
}

func (h *TicketHandler) serveLegacyImage(admin bool, thumbnail bool) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		userID, ok := notificationUserID(w, r)
		if !ok {
			return
		}
		data, err := h.service.TicketAttachmentFileByPath(r.Context(), userID, r.URL.Query().Get("path"), admin, thumbnail)
		if err != nil {
			h.writeError(w, err)
			return
		}
		w.Header().Set("Content-Type", data.ContentType)
		w.Header().Set("Cache-Control", "private, max-age=3600")
		w.Header().Set("Content-Disposition", `inline; filename="`+strings.ReplaceAll(data.Name, `"`, "")+`"`)
		http.ServeFile(w, r, data.Path)
	}
}

func (h *TicketHandler) adminList(w http.ResponseWriter, r *http.Request) {
	limit := queryInt(r.URL.Query().Get("limit"), 50, 1, 100)
	offset := queryInt(r.URL.Query().Get("offset"), 0, 0, 100000)
	query := AdminTicketQuery{
		Status:       strings.TrimSpace(r.URL.Query().Get("status")),
		Priority:     strings.TrimSpace(r.URL.Query().Get("priority")),
		UserID:       queryInt64Ptr(r.URL.Query().Get("user_id")),
		DepartmentID: queryInt64Ptr(r.URL.Query().Get("department_id")),
		AssigneeID:   queryInt64Ptr(r.URL.Query().Get("assignee_id")),
		Search:       strings.TrimSpace(r.URL.Query().Get("search")),
		Limit:        limit,
		Offset:       offset,
	}
	data, err := h.service.ListAdminTickets(r.Context(), query)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) adminDepartments(w http.ResponseWriter, r *http.Request) {
	data, err := h.service.AdminListTicketDepartments(r.Context(), AdminListQuery{
		Status: strings.TrimSpace(r.URL.Query().Get("status")),
		Search: strings.TrimSpace(r.URL.Query().Get("search")),
		Limit:  queryInt(r.URL.Query().Get("limit"), 100, 1, 200),
		Offset: queryInt(r.URL.Query().Get("offset"), 0, 0, 100000),
	})
	h.writeResult(w, data, err)
}

func (h *TicketHandler) adminSaveDepartment(w http.ResponseWriter, r *http.Request) {
	id := int64(0)
	if raw := chi.URLParam(r, "department_id"); raw != "" {
		parsed, ok := parseID(w, raw)
		if !ok {
			return
		}
		id = parsed
	}
	payload := map[string]any{}
	if !decodeTicketJSON(w, r, &payload) {
		return
	}
	data, err := h.service.AdminRawSave(r.Context(), "ticket_departments", id, payload)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) adminDeleteDepartment(w http.ResponseWriter, r *http.Request) {
	id, ok := parseID(w, chi.URLParam(r, "department_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminRawDelete(r.Context(), "ticket_departments", id)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) adminStaff(w http.ResponseWriter, r *http.Request) {
	var users []UserAccount
	err := h.service.db.WithContext(r.Context()).Where("role IN ?", []string{"admin", "super_admin"}).Order("id ASC").Find(&users).Error
	if err != nil {
		h.writeResult(w, nil, err)
		return
	}
	out := make([]map[string]any, 0, len(users))
	for _, user := range users {
		out = append(out, adminUserMap(user))
	}
	h.writeResult(w, out, nil)
}

func (h *TicketHandler) adminStats(w http.ResponseWriter, r *http.Request) {
	out := map[string]any{}
	for _, status := range []string{"open", "pending", "processing", "waiting_user", "answered", "resolved", "closed"} {
		var count int64
		_ = h.service.db.WithContext(r.Context()).Model(&Ticket{}).Where("status = ?", status).Count(&count).Error
		out[status] = count
	}
	var total int64
	_ = h.service.db.WithContext(r.Context()).Model(&Ticket{}).Count(&total).Error
	out["total"] = total
	h.writeResult(w, out, nil)
}

func (h *TicketHandler) adminDetail(w http.ResponseWriter, r *http.Request) {
	ticketID, ok := parseID(w, chi.URLParam(r, "ticket_id"))
	if !ok {
		return
	}
	data, err := h.service.GetAdminTicket(r.Context(), ticketID)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) adminUpdate(w http.ResponseWriter, r *http.Request) {
	adminID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	ticketID, ok := parseID(w, chi.URLParam(r, "ticket_id"))
	if !ok {
		return
	}
	var req AdminTicketUpdateRequest
	if !decodeTicketJSON(w, r, &req) {
		return
	}
	data, err := h.service.UpdateAdminTicket(r.Context(), adminID, ticketID, req)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) adminReply(w http.ResponseWriter, r *http.Request) {
	adminID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	ticketID, ok := parseID(w, chi.URLParam(r, "ticket_id"))
	if !ok {
		return
	}
	var req AdminTicketReplyRequest
	if !decodeTicketJSON(w, r, &req) {
		return
	}
	data, err := h.service.AdminReplyTicket(r.Context(), adminID, ticketID, req)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) adminTemplateReply(w http.ResponseWriter, r *http.Request) {
	adminID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	ticketID, ok := parseID(w, chi.URLParam(r, "ticket_id"))
	if !ok {
		return
	}
	var req AdminTicketTemplateReplyRequest
	if !decodeTicketJSON(w, r, &req) {
		return
	}
	data, err := h.service.AdminReplyTicketWithTemplate(r.Context(), adminID, ticketID, req)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) adminClose(w http.ResponseWriter, r *http.Request) {
	adminID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	ticketID, ok := parseID(w, chi.URLParam(r, "ticket_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminCloseTicket(r.Context(), adminID, ticketID)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) adminReopen(w http.ResponseWriter, r *http.Request) {
	adminID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	ticketID, ok := parseID(w, chi.URLParam(r, "ticket_id"))
	if !ok {
		return
	}
	data, err := h.service.AdminReopenTicket(r.Context(), adminID, ticketID)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) listReplyTemplates(w http.ResponseWriter, r *http.Request) {
	departmentID := queryInt64Ptr(r.URL.Query().Get("department_id"))
	includeDisabled := queryBool(r.URL.Query().Get("include_disabled"))
	data, err := h.service.ListTicketReplyTemplates(r.Context(), departmentID, includeDisabled)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) createReplyTemplate(w http.ResponseWriter, r *http.Request) {
	adminID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	var req TicketReplyTemplateRequest
	if !decodeTicketJSON(w, r, &req) {
		return
	}
	data, err := h.service.CreateTicketReplyTemplate(r.Context(), adminID, req)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) updateReplyTemplate(w http.ResponseWriter, r *http.Request) {
	adminID, ok := notificationUserID(w, r)
	if !ok {
		return
	}
	templateID, ok := parseID(w, chi.URLParam(r, "template_id"))
	if !ok {
		return
	}
	var req TicketReplyTemplateRequest
	if !decodeTicketJSON(w, r, &req) {
		return
	}
	data, err := h.service.UpdateTicketReplyTemplate(r.Context(), adminID, templateID, req)
	h.writeResult(w, data, err)
}

func (h *TicketHandler) deleteReplyTemplate(w http.ResponseWriter, r *http.Request) {
	templateID, ok := parseID(w, chi.URLParam(r, "template_id"))
	if !ok {
		return
	}
	data, err := h.service.DeleteTicketReplyTemplate(r.Context(), templateID)
	h.writeResult(w, data, err)
}

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

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

func decodeTicketJSON(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
}

func queryInt64Ptr(raw string) *int64 {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil
	}
	parsed, err := strconv.ParseInt(raw, 10, 64)
	if err != nil {
		return nil
	}
	return &parsed
}
