package agentmemory

import (
	"bytes"
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	defaultMeshXProvisionerTimeout  = 30 * time.Second
	defaultMeshXProvisionerTokenTTL = 5 * time.Minute
	meshXTrustedJWTHeader           = "X-AgentTeam-Verified-JWT"
	meshXWorkspaceHeader            = "X-Workspace-Id"
)

type EnableInput struct {
	UserID                     string
	WorkspaceID                string
	AgentID                    string
	TemplateLocale             string
	ImportExistingLocalContent bool
}

type Provisioner interface {
	EnableAgentCloudMemory(ctx context.Context, input EnableInput) error
}

type MeshXProvisionerOptions struct {
	BaseURL    string
	HTTPClient *http.Client
	TokenTTL   time.Duration
	Now        func() time.Time
}

type DependencyError struct {
	Status  int
	Message string
}

func (err DependencyError) Error() string {
	message := strings.TrimSpace(err.Message)
	if message != "" {
		return message
	}
	if text := http.StatusText(err.Status); text != "" {
		return text
	}
	return "meshx memory request failed"
}

func NewMeshXProvisioner(options MeshXProvisionerOptions) Provisioner {
	baseURL, err := normalizeBaseURL(options.BaseURL)
	if err != nil {
		return nil
	}
	client := options.HTTPClient
	if client == nil {
		client = &http.Client{Timeout: defaultMeshXProvisionerTimeout}
	}
	tokenTTL := options.TokenTTL
	if tokenTTL <= 0 {
		tokenTTL = defaultMeshXProvisionerTokenTTL
	}
	now := options.Now
	if now == nil {
		now = time.Now
	}
	return &meshXProvisioner{baseURL: baseURL, httpClient: client, tokenTTL: tokenTTL, now: now}
}

type meshXProvisioner struct {
	baseURL    *url.URL
	httpClient *http.Client
	tokenTTL   time.Duration
	now        func() time.Time
}

func (p *meshXProvisioner) EnableAgentCloudMemory(ctx context.Context, input EnableInput) error {
	if p == nil || p.baseURL == nil || p.httpClient == nil {
		return errors.New("meshx memory provisioner is not configured")
	}
	if strings.TrimSpace(input.UserID) == "" {
		return DependencyError{Status: http.StatusBadRequest, Message: "userID is required"}
	}
	if strings.TrimSpace(input.WorkspaceID) == "" {
		return DependencyError{Status: http.StatusBadRequest, Message: "workspaceID is required"}
	}
	if strings.TrimSpace(input.AgentID) == "" {
		return DependencyError{Status: http.StatusBadRequest, Message: "agentID is required"}
	}

	payload := enableRequest{
		ImportExistingLocalContent: input.ImportExistingLocalContent,
		TemplateLocale:             strings.TrimSpace(input.TemplateLocale),
	}
	body, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("encode meshx memory enable request: %w", err)
	}
	requestURL := p.url("/api/v1/memory/agents/" + url.PathEscape(strings.TrimSpace(input.AgentID)) + "/cloud-memory")
	req, err := p.newJSONRequest(ctx, http.MethodPost, requestURL, input, bytes.NewReader(body))
	if err != nil {
		return fmt.Errorf("prepare meshx memory enable request: %w", err)
	}
	resp, err := p.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("meshx memory enable request: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		msg, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return DependencyError{Status: resp.StatusCode, Message: strings.TrimSpace(string(msg))}
	}
	return nil
}

func (p *meshXProvisioner) newJSONRequest(ctx context.Context, method string, requestURL string, input EnableInput, body io.Reader) (*http.Request, error) {
	req, err := http.NewRequestWithContext(ctx, method, requestURL, body)
	if err != nil {
		return nil, err
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	req.Header.Set(meshXWorkspaceHeader, strings.TrimSpace(input.WorkspaceID))
	token, err := p.trustedJWT(input)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set(meshXTrustedJWTHeader, "Bearer "+token)
	return req, nil
}

func (p *meshXProvisioner) trustedJWT(input EnableInput) (string, error) {
	now := p.now().UTC()
	claims := map[string]any{
		"iss":                          "agentteam-server",
		"aud":                          "meshx",
		"sub":                          strings.TrimSpace(input.UserID),
		"agentteam_user_id":                 strings.TrimSpace(input.UserID),
		"workspace_ids":                []string{strings.TrimSpace(input.WorkspaceID)},
		"agent_ownership_workspace_id": strings.TrimSpace(input.WorkspaceID),
		"owned_agent_ids":              []string{strings.TrimSpace(input.AgentID)},
		"iat":                          now.Unix(),
		"exp":                          now.Add(p.tokenTTL).Unix(),
	}
	header, err := json.Marshal(map[string]string{"alg": "none", "typ": "JWT"})
	if err != nil {
		return "", fmt.Errorf("encode meshx memory trusted JWT header: %w", err)
	}
	payload, err := json.Marshal(claims)
	if err != nil {
		return "", fmt.Errorf("encode meshx memory trusted JWT claims: %w", err)
	}
	return base64.RawURLEncoding.EncodeToString(header) + "." + base64.RawURLEncoding.EncodeToString(payload) + ".", nil
}

func (p *meshXProvisioner) url(path string) string {
	u := *p.baseURL
	u.Path = singleJoiningSlash(p.baseURL.Path, path)
	u.RawPath = ""
	u.RawQuery = ""
	return u.String()
}

func normalizeBaseURL(raw string) (*url.URL, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" {
		return nil, errors.New("meshx base URL is required")
	}
	parsed, err := url.Parse(trimmed)
	if err != nil {
		return nil, err
	}
	if parsed.Scheme == "" || parsed.Host == "" {
		return nil, errors.New("meshx base URL must include scheme and host")
	}
	return parsed, nil
}

func singleJoiningSlash(left string, right string) string {
	leftSlash := strings.HasSuffix(left, "/")
	rightSlash := strings.HasPrefix(right, "/")
	switch {
	case leftSlash && rightSlash:
		return left + right[1:]
	case !leftSlash && !rightSlash:
		return left + "/" + right
	default:
		return left + right
	}
}

type enableRequest struct {
	ImportExistingLocalContent bool   `json:"importExistingLocalContent"`
	TemplateLocale             string `json:"templateLocale,omitempty"`
}
