package repocache

import (
	"context"
	"crypto/subtle"
	"encoding/json"
	"errors"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/api/meshxapi"
	service "github.com/tidbcloud/AgentTeam/internal/service/repocache"
	store "github.com/tidbcloud/AgentTeam/internal/store/repocache"
	"gorm.io/gorm"
)

type Service interface {
	Create(ctx context.Context, input service.CreateInput) (store.Cache, error)
	List(ctx context.Context, limit, offset int) ([]store.Cache, error)
	Update(ctx context.Context, id string, input service.UpdateInput) (store.Cache, error)
	Build(ctx context.Context, id, actor string) (store.Cache, error)
	CancelBuild(ctx context.Context, id, actor string) (store.Cache, error)
	Delete(ctx context.Context, id string) error
}

type Handler struct {
	service Service
	token   string
}

type createRequest struct {
	RemoteURL              string `json:"remoteUrl"`
	AutoRefreshEnabled     bool   `json:"autoRefreshEnabled"`
	RefreshIntervalSeconds int    `json:"refreshIntervalSeconds"`
}

type updateRequest struct {
	AutoRefreshEnabled     bool `json:"autoRefreshEnabled"`
	RefreshIntervalSeconds int  `json:"refreshIntervalSeconds"`
}

type cacheResponse struct {
	ID                     string     `json:"id"`
	RemoteURL              string     `json:"remoteUrl"`
	Status                 string     `json:"status"`
	AutoRefreshEnabled     bool       `json:"autoRefreshEnabled"`
	RefreshIntervalSeconds int        `json:"refreshIntervalSeconds"`
	LastSyncAt             *time.Time `json:"lastSyncAt,omitempty"`
	LastSuccessAt          *time.Time `json:"lastSuccessAt,omitempty"`
	NextSyncAt             *time.Time `json:"nextSyncAt,omitempty"`
	LastCommit             string     `json:"lastCommit,omitempty"`
	LastError              string     `json:"lastError,omitempty"`
	Info                   cacheInfo  `json:"info"`
}

type cacheInfo struct {
	SizeBytes int64 `json:"sizeBytes,omitempty"`
}

func NewHandler(service Service, token string) *Handler {
	normalizedToken := strings.TrimSpace(token)
	return &Handler{service: service, token: normalizedToken}
}

func RegisterRoutes(mux *http.ServeMux, handler *Handler) {
	if mux == nil || handler == nil {
		return
	}
	mux.Handle("GET /internal/admin/public-repository-caches", handler.authorize(http.HandlerFunc(handler.list)))
	mux.Handle("POST /internal/admin/public-repository-caches", handler.authorize(http.HandlerFunc(handler.create)))
	mux.Handle("PATCH /internal/admin/public-repository-caches/{id}", handler.authorize(http.HandlerFunc(handler.update)))
	mux.Handle("POST /internal/admin/public-repository-caches/{id}/build", handler.authorize(http.HandlerFunc(handler.build)))
	mux.Handle("POST /internal/admin/public-repository-caches/{id}/cancel", handler.authorize(http.HandlerFunc(handler.cancelBuild)))
	mux.Handle("DELETE /internal/admin/public-repository-caches/{id}", handler.authorize(http.HandlerFunc(handler.delete)))
}

func (h *Handler) authorize(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		expected := strings.TrimSpace(h.token)
		provided := strings.TrimSpace(strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer "))
		if expected == "" || len(expected) != len(provided) || subtle.ConstantTimeCompare([]byte(expected), []byte(provided)) != 1 {
			meshxapi.WriteError(w, meshxapi.NewHTTPError(http.StatusUnauthorized, "invalid MeshX admin service token"))
			return
		}
		next.ServeHTTP(w, r)
	})
}

func (h *Handler) list(w http.ResponseWriter, r *http.Request) {
	limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
	offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
	items, err := h.service.List(r.Context(), limit, offset)
	if err != nil {
		writeError(w, err)
		return
	}
	responses := make([]cacheResponse, 0, len(items))
	for _, item := range items {
		responses = append(responses, toResponse(item))
	}
	meshxapi.WriteResponse(w, meshxapi.OK(map[string]any{"items": responses}))
}

func (h *Handler) create(w http.ResponseWriter, r *http.Request) {
	var body createRequest
	if err := decodeJSON(r, &body); err != nil {
		meshxapi.WriteError(w, meshxapi.NewHTTPError(http.StatusBadRequest, "invalid request body"))
		return
	}
	created, err := h.service.Create(r.Context(), service.CreateInput{RemoteURL: body.RemoteURL, AutoRefreshEnabled: body.AutoRefreshEnabled, RefreshIntervalSecs: body.RefreshIntervalSeconds, Actor: actor(r)})
	if err != nil {
		writeError(w, err)
		return
	}
	meshxapi.WriteResponse(w, meshxapi.Created(toResponse(created)))
}

func (h *Handler) update(w http.ResponseWriter, r *http.Request) {
	var body updateRequest
	if err := decodeJSON(r, &body); err != nil {
		meshxapi.WriteError(w, meshxapi.NewHTTPError(http.StatusBadRequest, "invalid request body"))
		return
	}
	updated, err := h.service.Update(r.Context(), r.PathValue("id"), service.UpdateInput{AutoRefreshEnabled: body.AutoRefreshEnabled, RefreshIntervalSecs: body.RefreshIntervalSeconds, Actor: actor(r)})
	if err != nil {
		writeError(w, err)
		return
	}
	meshxapi.WriteResponse(w, meshxapi.OK(toResponse(updated)))
}

func (h *Handler) build(w http.ResponseWriter, r *http.Request) {
	updated, err := h.service.Build(r.Context(), r.PathValue("id"), actor(r))
	if err != nil {
		writeError(w, err)
		return
	}
	meshxapi.WriteResponse(w, meshxapi.OK(toResponse(updated)))
}

func (h *Handler) cancelBuild(w http.ResponseWriter, r *http.Request) {
	updated, err := h.service.CancelBuild(r.Context(), r.PathValue("id"), actor(r))
	if err != nil {
		writeError(w, err)
		return
	}
	meshxapi.WriteResponse(w, meshxapi.OK(toResponse(updated)))
}

func (h *Handler) delete(w http.ResponseWriter, r *http.Request) {
	if err := h.service.Delete(r.Context(), r.PathValue("id")); err != nil {
		writeError(w, err)
		return
	}
	meshxapi.WriteResponse(w, meshxapi.NoContent())
}

func decodeJSON(r *http.Request, target any) error {
	defer r.Body.Close()
	decoder := json.NewDecoder(r.Body)
	decoder.DisallowUnknownFields()
	return decoder.Decode(target)
}

func actor(r *http.Request) string {
	if value := strings.TrimSpace(r.Header.Get("X-Ops-Actor")); value != "" {
		return value
	}
	return "agentteam-ops"
}

func toResponse(cache store.Cache) cacheResponse {
	response := cacheResponse{ID: cache.ID, RemoteURL: cache.DisplayRemoteURL, Status: cache.Status, AutoRefreshEnabled: cache.AutoRefreshEnabled, RefreshIntervalSeconds: cache.RefreshIntervalSecs, LastSyncAt: cache.LastSyncAt, LastSuccessAt: cache.LastSuccessAt, NextSyncAt: cache.NextSyncAt, LastCommit: cache.LastCommit, LastError: cache.LastError}
	_ = json.Unmarshal([]byte(cache.Info), &response.Info)
	return response
}

func writeError(w http.ResponseWriter, err error) {
	status := http.StatusInternalServerError
	if errors.Is(err, gorm.ErrRecordNotFound) {
		status = http.StatusNotFound
	} else if strings.Contains(strings.ToLower(err.Error()), "invalid") || strings.Contains(strings.ToLower(err.Error()), "must") {
		status = http.StatusBadRequest
	} else if strings.Contains(strings.ToLower(err.Error()), "already building") || strings.Contains(strings.ToLower(err.Error()), "is building") {
		status = http.StatusConflict
	}
	meshxapi.WriteError(w, meshxapi.NewHTTPError(status, err.Error()))
}
