package memory

import (
	"context"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"net/http"

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/dto/converter"
	"github.com/tidbcloud/AgentTeam/internal/api/meshxapi"
	memoryservice "github.com/tidbcloud/AgentTeam/internal/service/memory"
)

const (
	maxMemoryCheckpointFiles     = 2_000
	maxMemoryCheckpointFileBytes = int64(16 << 20)
	maxMemoryCheckpointBytes     = int64(64 << 20)
	// The decoded payload may consume the full checkpoint budget. Base64 needs
	// 4/3 of that size; the remaining fixed allowance covers bounded paths and
	// JSON framing before any file content is decoded.
	maxMemoryCheckpointRequestBytes = ((maxMemoryCheckpointBytes + 2) / 3 * 4) + (4 << 20)
)

// ContentService is the narrow Memory content port consumed by exact-version
// API routes. Legacy Memory metadata and repository routes remain independent.
type ContentService interface {
	PublishMemoryCheckpoint(ctx context.Context, workspaceID, subject, memoryID string, input memoryservice.MemoryCheckpointMutation) (memoryservice.MemoryContentVersion, error)
	ListMemoryContentVersions(ctx context.Context, workspaceID, subject, memoryID string, page, perPage int) (memoryservice.MemoryContentVersionPage, error)
	CompareMemoryContentVersions(ctx context.Context, workspaceID, subject, memoryID, beforeVersionID, afterVersionID string) ([]memoryservice.MemoryContentCompareEntry, error)
	CreateMemoryContentBackup(ctx context.Context, workspaceID, subject, memoryID string, input memoryservice.MemoryBackupMutation) (memoryservice.MemoryContentBackup, error)
	ListMemoryContentBackups(ctx context.Context, workspaceID, subject, memoryID string, limit int) ([]memoryservice.MemoryContentBackup, error)
	RestoreMemoryContentVersion(ctx context.Context, workspaceID, subject, memoryID string, input memoryservice.MemoryRestoreMutation) (memoryservice.MemoryContentVersion, error)
	OpenMemoryContentArchive(ctx context.Context, workspaceID, subject, memoryID, versionID string) (memoryservice.MemoryContentArchive, error)
}

type ContentHandler struct{ service ContentService }

func newContentHandler(service Service) *ContentHandler {
	content, _ := service.(ContentService)
	return &ContentHandler{service: content}
}

func (h *ContentHandler) ensureService() error {
	if h == nil || h.service == nil {
		return meshxapi.NewHTTPError(http.StatusServiceUnavailable, "Memory content API is not configured")
	}
	return nil
}

// PublishCheckpoint handles POST /api/v1/memory/{memory_id}/checkpoints.
// It publishes one complete, bounded local checkpoint against an exact cloud
// head and returns the immutable version acknowledged by the server.
func (h *ContentHandler) PublishCheckpoint(r meshxapi.AuthenticatedJSON[dto.MemoryCheckpointPublishRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	files, err := decodeMemoryCheckpointFiles(r.Body.Files)
	if err != nil {
		return meshxapi.Response{}, meshxapi.NewHTTPError(http.StatusBadRequest, err.Error())
	}
	created, err := h.service.PublishMemoryCheckpoint(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("memory_id"), memoryservice.MemoryCheckpointMutation{
		OperationID: r.Body.OperationID, ReplicaID: r.Body.ReplicaID,
		ExpectedVersionID: r.Body.ExpectedVersionID, Generation: r.Body.Generation,
		CoveredFromSequence: r.Body.CoveredFromSequence, CoveredThroughSequence: r.Body.CoveredThroughSequence,
		Files: files,
	})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToMemoryContentVersion(created)), nil
}

// ListContentVersions handles GET /api/v1/memory/{memory_id}/versions.
// It returns bounded Memory-owned exact-version projections with stable paging.
func (h *ContentHandler) ListContentVersions(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	page, perPage := pagination(r.Request)
	result, err := h.service.ListMemoryContentVersions(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("memory_id"), page, perPage)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToMemoryContentVersionListResponse(result)), nil
}

// CompareContentVersions handles GET /api/v1/memory/{memory_id}/versions:compare.
// It compares two exact immutable versions from the Memory's active lineage.
func (h *ContentHandler) CompareContentVersions(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	query := r.Request.URL.Query()
	result, err := h.service.CompareMemoryContentVersions(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("memory_id"), query.Get("beforeVersionId"), query.Get("afterVersionId"))
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToMemoryContentCompareResponse(result)), nil
}

// CreateContentBackup handles POST /api/v1/memory/{memory_id}/backups.
// It pins the exact active cloud version under the supplied product label.
func (h *ContentHandler) CreateContentBackup(r meshxapi.AuthenticatedJSON[dto.MemoryContentBackupCreateRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	created, err := h.service.CreateMemoryContentBackup(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("memory_id"), memoryservice.MemoryBackupMutation{
		ExpectedVersionID: r.Body.ExpectedVersionID, Generation: r.Body.Generation, Label: r.Body.Label,
	})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToMemoryContentBackup(created)), nil
}

// ListContentBackups handles GET /api/v1/memory/{memory_id}/backups.
// It returns at most 64 exact backup labels for one authorized Memory.
func (h *ContentHandler) ListContentBackups(r meshxapi.AuthenticatedRequest) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	limit := parsePositiveInt(r.Request.URL.Query().Get("limit"), 64)
	result, err := h.service.ListMemoryContentBackups(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("memory_id"), limit)
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.OK(converter.ToMemoryContentBackupListResponse(result)), nil
}

// RestoreContentVersion handles POST /api/v1/memory/{memory_id}/versions:restore.
// It publishes the target snapshot as a new immutable cloud version rather than
// rewinding either the remote lineage or a client's live local working set.
func (h *ContentHandler) RestoreContentVersion(r meshxapi.AuthenticatedJSON[dto.MemoryContentRestoreRequest]) (meshxapi.Response, error) {
	if err := h.ensureService(); err != nil {
		return meshxapi.Response{}, err
	}
	created, err := h.service.RestoreMemoryContentVersion(r.Context(), r.WorkspaceID(), r.Subject(), r.Request.PathValue("memory_id"), memoryservice.MemoryRestoreMutation{
		OperationID: r.Body.OperationID, ExpectedVersionID: r.Body.ExpectedVersionID,
		TargetVersionID: r.Body.TargetVersionID, Generation: r.Body.Generation,
	})
	if err != nil {
		return meshxapi.Response{}, err
	}
	return meshxapi.Created(converter.ToMemoryContentVersion(created)), nil
}

// OpenContentArchive handles GET /api/v1/memory/{memory_id}/versions/{version_id}/archive.
// It streams one exact bounded Memory tree as ZIP after live product ACL checks.
func (h *ContentHandler) OpenContentArchive(w http.ResponseWriter, request *http.Request) {
	if err := h.ensureService(); err != nil {
		meshxapi.WriteError(w, err)
		return
	}
	r, err := meshxapi.NewAuthenticatedRequest(request)
	if err != nil {
		meshxapi.WriteError(w, err)
		return
	}
	archive, err := h.service.OpenMemoryContentArchive(r.Context(), r.WorkspaceID(), r.Subject(), request.PathValue("memory_id"), request.PathValue("version_id"))
	if err != nil {
		writeMemoryStreamError(w, err)
		return
	}
	defer archive.Body.Close()
	w.Header().Set("Content-Type", "application/zip")
	w.Header().Set("Content-Disposition", `attachment; filename="memory-version.zip"`)
	_, _ = io.Copy(w, archive.Body)
}

func decodeMemoryCheckpointFiles(inputs []dto.MemoryContentFileInput) ([]memoryservice.NativeMemoryContentFile, error) {
	if len(inputs) > maxMemoryCheckpointFiles {
		return nil, fmt.Errorf("memory checkpoint exceeds %d file limit", maxMemoryCheckpointFiles)
	}
	files := make([]memoryservice.NativeMemoryContentFile, 0, len(inputs))
	var total int64
	for _, input := range inputs {
		decoded, err := base64.StdEncoding.Strict().DecodeString(input.ContentBase64)
		if err != nil {
			return nil, fmt.Errorf("memory checkpoint file %q contentBase64 is invalid", input.Path)
		}
		size := int64(len(decoded))
		if size > maxMemoryCheckpointFileBytes || total > maxMemoryCheckpointBytes-size {
			return nil, fmt.Errorf("memory checkpoint exceeds byte limits")
		}
		total += size
		files = append(files, memoryservice.NativeMemoryContentFile{Path: input.Path, Content: decoded, Executable: input.Executable})
	}
	return files, nil
}

func writeMemoryStreamError(w http.ResponseWriter, err error) {
	if errors.Is(err, memoryservice.ErrUnauthorized) || errors.Is(err, memoryservice.ErrInvalidArgument) || errors.Is(err, memoryservice.ErrForbidden) || errors.Is(err, memoryservice.ErrNotFound) || errors.Is(err, memoryservice.ErrConflict) {
		meshxapi.WriteError(w, meshxapi.NewHTTPError(memoryservice.HTTPStatus(err), err.Error()))
		return
	}
	meshxapi.WriteError(w, err)
}
