package exec

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"mime/multipart"
	"net/http"
	"net/textproto"
	"path/filepath"

	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/apiclient"
	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/workspacefs"
)

// isAllowedImageType restricts to JPEG/PNG/GIF/WebP per the avatar contract.
func isAllowedImageType(contentType string) bool {
	switch contentType {
	case "image/jpeg", "image/png", "image/gif", "image/webp":
		return true
	}
	return false
}

// profileRemote adapts the runtime client to avatar operations. WorkDir
// is the daemon-injected session base for safe path resolution; when empty the
// adapter refuses raw file paths.
type profileRemote struct {
	client  *apiclient.Client
	workDir string
	natural naturalMutationTransport

	// beforeSecureOpen is a deterministic test seam for a final-component
	// symlink swap between path resolution and the kernel no-follow open.
	beforeSecureOpen func(string)
}

const maxAvatarBytes = 5 * 1024 * 1024 // 5MB

func (remote profileRemote) naturalTransport() naturalMutationTransport {
	transport := remote.natural
	transport.client = remote.client
	return transport
}

func (remote profileRemote) setAvatar(ctx context.Context, imagePath string) (profileSetAvatarResult, error) {
	if err := requireRuntimeClient(remote.client); err != nil {
		return profileSetAvatarResult{}, err
	}
	if remote.workDir == "" {
		return profileSetAvatarResult{}, &Error{Code: CodePermissionDenied, Problem: "file access requires a session workdir"}
	}
	absBase, err := filepath.Abs(remote.workDir)
	if err != nil {
		return profileSetAvatarResult{}, &Error{Code: CodeInternal, Problem: "invalid workdir"}
	}
	resolved, err := workspacefs.ResolvePathWithinBase(absBase, imagePath)
	if err != nil {
		return profileSetAvatarResult{}, &Error{Code: CodePermissionDenied, Problem: "file path outside session base"}
	}
	if remote.beforeSecureOpen != nil {
		remote.beforeSecureOpen(resolved)
	}
	blob, err := secureReadRegularFile(absBase, resolved, maxAvatarBytes)
	if err != nil {
		switch {
		case errors.Is(err, errSecureFileNotFound):
			return profileSetAvatarResult{}, &Error{Code: CodeNotFound, Problem: "file not found"}
		case errors.Is(err, errSecureFileTooLarge):
			return profileSetAvatarResult{}, &Error{Code: CodeValidation, Problem: "file too large (max 5MB)"}
		case errors.Is(err, errSecureFileRejected):
			return profileSetAvatarResult{}, &Error{Code: CodePermissionDenied, Problem: "file must be a regular file without symlinks or reparse points"}
		default:
			return profileSetAvatarResult{}, &Error{Code: CodeInternal, Problem: "cannot read file"}
		}
	}
	contentType := http.DetectContentType(blob)
	if !isAllowedImageType(contentType) {
		return profileSetAvatarResult{}, &Error{Code: CodeValidation, Problem: "only JPEG/PNG/GIF/WebP images are allowed"}
	}
	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	part, err := writer.CreatePart(textproto.MIMEHeader{
		"Content-Disposition": {fmt.Sprintf(`form-data; name="file"; filename="%s"`, filepath.Base(imagePath))},
		"Content-Type":        {contentType},
	})
	if err != nil {
		return profileSetAvatarResult{}, err
	}
	if _, err := part.Write(blob); err != nil {
		return profileSetAvatarResult{}, err
	}
	if err := writer.Close(); err != nil {
		return profileSetAvatarResult{}, err
	}
	var result profileSetAvatarResult
	if err := remote.naturalTransport().multipartRaw(
		ctx,
		remote.client.AgentRuntimePath("/avatar"),
		body.Bytes(),
		writer.FormDataContentType(),
		&result,
	); err != nil {
		return profileSetAvatarResult{}, err
	}
	return result, nil
}
