package kb

import (
	"context"
	"errors"
	"io"
	"net/http"
	"os"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/api/meshxapi"
	kbservice "github.com/tidbcloud/AgentTeam/internal/service/kb"
)

const uaDashboardMultipartOverheadBytes = 1 << 20

type DashboardService interface {
	AuthorizeUADashboardImport(context.Context, string, string, string, string) (kbservice.UADashboardImportLimits, error)
	GetUADashboardStatus(context.Context, string, string, string, string) (kbservice.UADashboardStatus, error)
	GetUADashboardSourcePreview(context.Context, string, string, string, string) (kbservice.UADashboardSourcePreview, error)
	ImportUADashboard(context.Context, string, string, string, string, kbservice.UADashboardImport) (kbservice.UADashboardStatus, error)
	ReadUADashboardFile(context.Context, string, string, string, string, string) (kbservice.UADashboardFile, error)
}

// SourcePreview handles GET /api/v1/kb/{kb_id}/sources/{source_id}/dashboard/source-preview.
// It resolves the narrow canonical/mirror mapping only after the
// user asks to view a node's source. Dashboard first paint never calls it.
func (h *DashboardHandler) SourcePreview(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	preview, err := h.service.GetUADashboardSourcePreview(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("source_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(dto.UADashboardSourcePreviewResponse{RepoOwner: preview.RepoOwner, RepoName: preview.RepoName, SourceCommitSHA: preview.SourceCommitSHA}), nil
}

type DashboardHandler struct{ service DashboardService }

// Status handles GET /api/v1/kb/{kb_id}/sources/{source_id}/dashboard.
// It returns the source-scoped current dashboard publication without reading AGS.
func (h *DashboardHandler) Status(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	status, err := h.service.GetUADashboardStatus(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("kb_id"), r.Request.PathValue("source_id"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToUADashboardStatusResponse(status)), nil
}

// importHTTP handles POST /api/v1/kb/{kb_id}/sources/{source_id}/dashboard/import.
// It streams exactly one bounded multipart archive into a private temporary file.
func (h *DashboardHandler) importHTTP() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		request, ok := h.authenticateHTTP(w, r)
		if !ok {
			return
		}
		limits, err := h.service.AuthorizeUADashboardImport(request.Context(), request.WorkspaceID(), request.Subject(), r.PathValue("kb_id"), r.PathValue("source_id"))
		if err != nil {
			writeKBDashboardHTTPError(w, err)
			return
		}
		limit := limits.HTTPBytes
		r.Body = http.MaxBytesReader(w, r.Body, limit+uaDashboardMultipartOverheadBytes)
		if err := r.ParseMultipartForm(limit); err != nil {
			var maxBytes *http.MaxBytesError
			if errors.As(err, &maxBytes) {
				writeKBDashboardHTTPError(w, meshxapi.NewHTTPError(http.StatusRequestEntityTooLarge, "Dashboard archive exceeds limits"))
				return
			}
			writeKBDashboardHTTPError(w, meshxapi.NewHTTPError(http.StatusBadRequest, "Invalid dashboard import form"))
			return
		}
		defer r.MultipartForm.RemoveAll()
		if len(r.MultipartForm.Value) != 0 || len(r.MultipartForm.File) != 1 || len(r.MultipartForm.File["file"]) != 1 {
			writeKBDashboardHTTPError(w, meshxapi.NewHTTPError(http.StatusBadRequest, "Provide exactly one dashboard file"))
			return
		}
		file, header, err := r.FormFile("file")
		if err != nil {
			writeKBDashboardHTTPError(w, meshxapi.NewHTTPError(http.StatusBadRequest, "Invalid dashboard import file"))
			return
		}
		defer file.Close()
		temp, err := os.CreateTemp("", "meshx-ua-dashboard-upload-*")
		if err != nil {
			writeKBDashboardHTTPError(w, err)
			return
		}
		path := temp.Name()
		defer os.Remove(path)
		written, copyErr := io.Copy(temp, io.LimitReader(file, limit+1))
		closeErr := temp.Close()
		if copyErr != nil || closeErr != nil {
			writeKBDashboardHTTPError(w, meshxapi.NewHTTPError(http.StatusBadRequest, "Could not read dashboard import file"))
			return
		}
		if written > limit {
			writeKBDashboardHTTPError(w, meshxapi.NewHTTPError(http.StatusRequestEntityTooLarge, "Dashboard archive exceeds limits"))
			return
		}
		status, err := h.service.ImportUADashboard(request.Context(), request.WorkspaceID(), request.Subject(), r.PathValue("kb_id"), r.PathValue("source_id"), kbservice.UADashboardImport{ArchivePath: path, Filename: header.Filename})
		if err != nil {
			writeKBDashboardHTTPError(w, err)
			return
		}
		meshxapi.WriteResponse(w, meshxapi.OK(converter.ToUADashboardStatusResponse(status)))
	})
}

// fileHTTP handles GET /api/v1/kb/{kb_id}/sources/{source_id}/dashboard/files/{path}.
// It authorizes and resolves the current publication before evaluating ETag.
func (h *DashboardHandler) fileHTTP() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		request, ok := h.authenticateHTTP(w, r)
		if !ok {
			return
		}
		file, err := h.service.ReadUADashboardFile(request.Context(), request.WorkspaceID(), request.Subject(), r.PathValue("kb_id"), r.PathValue("source_id"), r.PathValue("path"))
		if err != nil {
			writeKBDashboardHTTPError(w, err)
			return
		}
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		w.Header().Set("Cache-Control", "private")
		w.Header().Set("ETag", file.ETag)
		if strings.TrimSpace(r.Header.Get("If-None-Match")) == file.ETag {
			w.WriteHeader(http.StatusNotModified)
			return
		}
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write(file.Content)
	})
}

func (h *DashboardHandler) authenticateHTTP(w http.ResponseWriter, r *http.Request) (meshxapi.AuthenticatedRequest, bool) {
	if h == nil || h.service == nil {
		meshxapi.WriteError(w, meshxapi.NewHTTPError(http.StatusServiceUnavailable, "KB Dashboard API is not configured"))
		return meshxapi.AuthenticatedRequest{}, false
	}
	request, err := meshxapi.NewAuthenticatedRequest(r)
	if err != nil {
		writeKBDashboardHTTPError(w, err)
		return meshxapi.AuthenticatedRequest{}, false
	}
	return request, true
}

func writeKBDashboardHTTPError(w http.ResponseWriter, err error) {
	if errors.Is(err, kbservice.ErrUnauthorized) || errors.Is(err, kbservice.ErrInvalidArgument) || errors.Is(err, kbservice.ErrForbidden) || errors.Is(err, kbservice.ErrNotFound) || errors.Is(err, kbservice.ErrConflict) {
		status := kbservice.HTTPStatus(err)
		if details, ok := kbservice.Details(err).(map[string]any); ok {
			if override, ok := details["httpStatus"].(int); ok {
				status = override
			}
		}
		meshxapi.WriteError(w, meshxapi.HTTPError{Status: status, Message: err.Error(), Code: kbservice.Code(err), Cause: err})
		return
	}
	meshxapi.WriteError(w, err)
}
