package agents

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

	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/plan"
)

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

// MeshXRealizerOptions configures the MeshX-backed Role realization adapter.
type MeshXRealizerOptions struct {
	BaseURL    string
	HTTPClient *http.Client
	TokenTTL   time.Duration
	Now        func() time.Time
}

// NewMeshXRealizer returns a RoleRealizer backed by the canonical MeshX Role
// API. A nil return means the MeshX endpoint is not configured.
func NewMeshXRealizer(options MeshXRealizerOptions) RoleRealizer {
	baseURL, err := normalizeMeshXBaseURL(options.BaseURL)
	if err != nil {
		return nil
	}
	client := options.HTTPClient
	if client == nil {
		client = &http.Client{Timeout: defaultMeshXRealizerTimeout}
	}
	tokenTTL := options.TokenTTL
	if tokenTTL <= 0 {
		tokenTTL = defaultMeshXRealizerTokenTTL
	}
	now := options.Now
	if now == nil {
		now = time.Now
	}
	return &meshXRealizer{baseURL: baseURL, httpClient: client, tokenTTL: tokenTTL, now: now}
}

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

func (r *meshXRealizer) RealizeRoleForAgent(ctx context.Context, input RealizeRoleForAgentInput) (RealizationResult, error) {
	if r == nil || r.baseURL == nil || r.httpClient == nil {
		return RealizationResult{}, errors.New("meshx role realizer is not configured")
	}
	if strings.TrimSpace(input.UserID) == "" {
		return RealizationResult{}, &ValidationError{Message: "userID is required"}
	}
	if strings.TrimSpace(input.WorkspaceID) == "" {
		return RealizationResult{}, &ValidationError{Message: "workspaceID is required"}
	}
	if strings.TrimSpace(input.AgentID) == "" {
		return RealizationResult{}, &ValidationError{Message: "agentID is required"}
	}
	requestURL, body, preset, err := r.realizationRequest(input)
	if err != nil {
		return RealizationResult{}, err
	}
	req, err := r.newJSONRequest(ctx, http.MethodPost, requestURL, input, bytes.NewReader(body))
	if err != nil {
		return RealizationResult{}, fmt.Errorf("prepare meshx role realization request: %w", err)
	}

	resp, err := r.httpClient.Do(req)
	if err != nil {
		return RealizationResult{}, fmt.Errorf("meshx role realization request: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		msg, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return RealizationResult{}, fmt.Errorf("meshx role realization failed: status %d: %s", resp.StatusCode, strings.TrimSpace(string(msg)))
	}
	if preset {
		var response meshXPresetRoleRealizationResponse
		if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
			return RealizationResult{}, fmt.Errorf("decode meshx preset role realization response: %w", err)
		}
		result := response.Realization.toResult()
		if strings.TrimSpace(result.RoleID) == "" {
			result.RoleID = strings.TrimSpace(response.Role.ID)
		}
		return result, nil
	}
	var response meshXRoleRealizationResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return RealizationResult{}, fmt.Errorf("decode meshx role realization response: %w", err)
	}
	return response.toResult(), nil
}

func (r *meshXRealizer) RoleMaterialization(ctx context.Context, input RealizeRoleForAgentInput) (agent.AgentRoleMaterialization, error) {
	if r == nil || r.baseURL == nil || r.httpClient == nil {
		return agent.AgentRoleMaterialization{}, errors.New("meshx role realizer is not configured")
	}
	if strings.TrimSpace(input.UserID) == "" {
		return agent.AgentRoleMaterialization{}, &ValidationError{Message: "userID is required"}
	}
	if strings.TrimSpace(input.WorkspaceID) == "" {
		return agent.AgentRoleMaterialization{}, &ValidationError{Message: "workspaceID is required"}
	}
	requestURL, err := r.materializationRequestURL(input)
	if err != nil {
		return agent.AgentRoleMaterialization{}, err
	}
	req, err := r.newJSONRequest(ctx, http.MethodGet, requestURL, input, nil)
	if err != nil {
		return agent.AgentRoleMaterialization{}, fmt.Errorf("prepare meshx role materialization request: %w", err)
	}

	resp, err := r.httpClient.Do(req)
	if err != nil {
		return agent.AgentRoleMaterialization{}, fmt.Errorf("meshx role materialization request: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		msg, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return agent.AgentRoleMaterialization{}, fmt.Errorf("meshx role materialization failed: status %d: %s", resp.StatusCode, strings.TrimSpace(string(msg)))
	}
	var role meshXRoleDetail
	if err := json.NewDecoder(resp.Body).Decode(&role); err != nil {
		return agent.AgentRoleMaterialization{}, fmt.Errorf("decode meshx role materialization response: %w", err)
	}
	return role.toMaterialization(), nil
}

func (r *meshXRealizer) RoleResourceImpact(ctx context.Context, input RealizeRoleForAgentInput) (ResourceImpact, error) {
	if r == nil || r.baseURL == nil || r.httpClient == nil {
		return ResourceImpact{}, errors.New("meshx role realizer is not configured")
	}
	if strings.TrimSpace(input.UserID) == "" {
		return ResourceImpact{}, &ValidationError{Message: "userID is required"}
	}
	if strings.TrimSpace(input.WorkspaceID) == "" {
		return ResourceImpact{}, &ValidationError{Message: "workspaceID is required"}
	}
	if strings.TrimSpace(input.AgentID) == "" {
		return ResourceImpact{}, &ValidationError{Message: "agentID is required"}
	}
	requestURL, body, err := r.impactRequest(input)
	if err != nil {
		return ResourceImpact{}, err
	}
	req, err := r.newJSONRequest(ctx, http.MethodPost, requestURL, input, bytes.NewReader(body))
	if err != nil {
		return ResourceImpact{}, fmt.Errorf("prepare meshx role impact request: %w", err)
	}
	resp, err := r.httpClient.Do(req)
	if err != nil {
		return ResourceImpact{}, fmt.Errorf("meshx role impact request: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		msg, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return ResourceImpact{}, fmt.Errorf("meshx role impact failed: status %d: %s", resp.StatusCode, strings.TrimSpace(string(msg)))
	}
	var response meshXRoleRealizationImpactResponse
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
		return ResourceImpact{}, fmt.Errorf("decode meshx role impact response: %w", err)
	}
	return ResourceImpact{ManagedMemories: response.ManagedMemories, SkillRepositories: response.SkillRepositories}, nil
}

func (r *meshXRealizer) CountWorkspaceResource(ctx context.Context, input RealizeRoleForAgentInput, metric plan.MetricKey, scope plan.QuotaScope) (int64, error) {
	if r == nil || r.baseURL == nil || r.httpClient == nil {
		return 0, errors.New("meshx role realizer is not configured")
	}
	if scope != "" && scope != plan.QuotaScopeWorkspaceMember {
		return 0, &ValidationError{Message: "quota scope is invalid"}
	}
	path := ""
	switch metric {
	case plan.MetricManagedMemories:
		path = "/api/v1/internal/memory/count"
	case plan.MetricSkillRepositories:
		path = "/api/v1/internal/skills/count"
	default:
		return 0, &ValidationError{Message: "metric is invalid"}
	}
	req, err := r.newJSONRequest(ctx, http.MethodGet, r.url(path), input, nil)
	if err != nil {
		return 0, fmt.Errorf("prepare meshx resource count request: %w", err)
	}
	req.Header.Set("Accept", "application/json")
	resp, err := r.httpClient.Do(req)
	if err != nil {
		return 0, fmt.Errorf("meshx resource count request: %w", err)
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		msg, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
		return 0, fmt.Errorf("meshx resource count failed: status %d: %s", resp.StatusCode, strings.TrimSpace(string(msg)))
	}
	var response struct {
		Total int64 `json:"total"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&response); err != nil && !errors.Is(err, io.EOF) {
		return 0, fmt.Errorf("decode meshx resource count response: %w", err)
	}
	return response.Total, nil
}

func (r *meshXRealizer) newJSONRequest(ctx context.Context, method string, requestURL string, input RealizeRoleForAgentInput, 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 := r.trustedJWT(input)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set(meshXTrustedJWTHeader, "Bearer "+token)
	return req, nil
}

func (r *meshXRealizer) realizationRequest(input RealizeRoleForAgentInput) (string, []byte, bool, error) {
	payload := meshXRoleRealizationRequest{
		AgentID:        strings.TrimSpace(input.AgentID),
		RoleVersionID:  strings.TrimSpace(input.Source.VersionID),
		DryRun:         input.Realization.DryRun,
		Apply:          input.Realization.Apply,
		SkipOptional:   input.Realization.SkipOptional,
		StorageScope:   strings.TrimSpace(input.Realization.StorageScope),
		TemplateLocale: strings.TrimSpace(input.Realization.TemplateLocale),
	}
	switch strings.TrimSpace(input.Source.Kind) {
	case "preset":
		if strings.TrimSpace(input.Source.PresetID) == "" {
			return "", nil, false, &ValidationError{Message: "source.presetId is required"}
		}
		body, err := json.Marshal(meshXPresetRoleRealizationRequest{
			PresetID:       strings.TrimSpace(input.Source.PresetID),
			Locale:         strings.TrimSpace(input.Source.Locale),
			DisplayName:    strings.TrimSpace(input.RoleDisplayName),
			AgentID:        payload.AgentID,
			DryRun:         payload.DryRun,
			Apply:          payload.Apply,
			SkipOptional:   payload.SkipOptional,
			StorageScope:   payload.StorageScope,
			TemplateLocale: payload.TemplateLocale,
		})
		if err != nil {
			return "", nil, false, fmt.Errorf("encode meshx preset role realization request: %w", err)
		}
		return r.url("/api/v1/roles/presets:realize"), body, true, nil
	case "my-role":
		if strings.TrimSpace(input.Source.RoleID) == "" {
			return "", nil, false, &ValidationError{Message: "source.roleId is required"}
		}
		if strings.TrimSpace(input.Source.VersionID) == "" {
			return "", nil, false, &ValidationError{Message: "source.versionId is required"}
		}
		body, err := json.Marshal(payload)
		if err != nil {
			return "", nil, false, fmt.Errorf("encode meshx role realization request: %w", err)
		}
		return r.url("/api/v1/roles/" + url.PathEscape(strings.TrimSpace(input.Source.RoleID)) + "/realize"), body, false, nil
	default:
		return "", nil, false, &ValidationError{Message: "source.kind must be preset or my-role"}
	}
}

func (r *meshXRealizer) impactRequest(input RealizeRoleForAgentInput) (string, []byte, error) {
	_, body, preset, err := r.realizationRequest(input)
	if err != nil {
		return "", nil, err
	}
	if preset {
		return r.url("/api/v1/internal/roles/presets:realization-impact"), body, nil
	}
	roleID := strings.TrimSpace(input.Source.RoleID)
	if roleID == "" {
		return "", nil, &ValidationError{Message: "source.roleId is required"}
	}
	return r.url("/api/v1/internal/roles/" + url.PathEscape(roleID) + "/realization-impact"), body, nil
}

func (r *meshXRealizer) materializationRequestURL(input RealizeRoleForAgentInput) (string, error) {
	switch strings.TrimSpace(input.Source.Kind) {
	case "preset":
		if strings.TrimSpace(input.Source.PresetID) == "" {
			return "", &ValidationError{Message: "source.presetId is required"}
		}
		values := url.Values{}
		if locale := strings.TrimSpace(input.Source.Locale); locale != "" {
			values.Set("locale", locale)
		}
		rawURL := r.url("/api/v1/roles/presets/" + url.PathEscape(strings.TrimSpace(input.Source.PresetID)))
		if encoded := values.Encode(); encoded != "" {
			rawURL += "?" + encoded
		}
		return rawURL, nil
	case "my-role":
		if strings.TrimSpace(input.Source.RoleID) == "" {
			return "", &ValidationError{Message: "source.roleId is required"}
		}
		values := url.Values{}
		if versionID := strings.TrimSpace(input.Source.VersionID); versionID != "" {
			values.Set("versionId", versionID)
		}
		rawURL := r.url("/api/v1/roles/" + url.PathEscape(strings.TrimSpace(input.Source.RoleID)))
		if encoded := values.Encode(); encoded != "" {
			rawURL += "?" + encoded
		}
		return rawURL, nil
	default:
		return "", &ValidationError{Message: "source.kind must be preset or my-role"}
	}
}

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

func (r *meshXRealizer) trustedJWT(input RealizeRoleForAgentInput) (string, error) {
	now := r.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(r.tokenTTL).Unix(),
	}
	header, err := json.Marshal(map[string]string{"alg": "none", "typ": "JWT"})
	if err != nil {
		return "", fmt.Errorf("encode meshx role trusted JWT header: %w", err)
	}
	payload, err := json.Marshal(claims)
	if err != nil {
		return "", fmt.Errorf("encode meshx role trusted JWT claims: %w", err)
	}
	return base64.RawURLEncoding.EncodeToString(header) + "." + base64.RawURLEncoding.EncodeToString(payload) + ".", nil
}

type meshXRoleRealizationRequest struct {
	AgentID        string `json:"agentId"`
	RoleVersionID  string `json:"roleVersionId,omitempty"`
	DryRun         bool   `json:"dryRun,omitempty"`
	Apply          bool   `json:"apply,omitempty"`
	SkipOptional   bool   `json:"skipOptional,omitempty"`
	StorageScope   string `json:"storageScope,omitempty"`
	TemplateLocale string `json:"templateLocale,omitempty"`
}

type meshXPresetRoleRealizationRequest struct {
	PresetID       string `json:"presetId"`
	Locale         string `json:"locale,omitempty"`
	DisplayName    string `json:"displayName,omitempty"`
	AgentID        string `json:"agentId"`
	DryRun         bool   `json:"dryRun,omitempty"`
	Apply          bool   `json:"apply,omitempty"`
	SkipOptional   bool   `json:"skipOptional,omitempty"`
	StorageScope   string `json:"storageScope,omitempty"`
	TemplateLocale string `json:"templateLocale,omitempty"`
}

type meshXPresetRoleRealizationResponse struct {
	Role        meshXRole                    `json:"role"`
	Realization meshXRoleRealizationResponse `json:"realization"`
}

type meshXRoleRealizationImpactResponse struct {
	ManagedMemories   int64 `json:"managedMemories"`
	SkillRepositories int64 `json:"skillRepositories"`
}

type meshXRole struct {
	ID string `json:"id"`
}

type meshXRoleDetail struct {
	ID                    string            `json:"id"`
	Source                string            `json:"source"`
	RoleVersionID         string            `json:"roleVersionId"`
	SourcePresetID        string            `json:"sourcePresetId"`
	SourcePresetVersionID string            `json:"sourcePresetVersionId"`
	RolePackageManifest   meshXRoleManifest `json:"rolePackageManifest"`
}

type meshXRoleManifest struct {
	Items []meshXRoleManifestItem `json:"items"`
}

type meshXRoleManifestItem struct {
	Type        string `json:"type"`
	Name        string `json:"name,omitempty"`
	Description string `json:"description,omitempty"`
	Required    bool   `json:"required,omitempty"`
	Action      string `json:"action,omitempty"`
	ResourceID  string `json:"resourceId,omitempty"`
	AccessLevel string `json:"accessLevel,omitempty"`
	Branch      string `json:"branch,omitempty"`
	Content     string `json:"content,omitempty"`
}

func (r meshXRoleDetail) toMaterialization() agent.AgentRoleMaterialization {
	items := make([]agent.AgentRoleMaterializationItem, 0, len(r.RolePackageManifest.Items))
	activeDefinitionPath := agent.CloudMemoryIdentityPath
	for _, item := range r.RolePackageManifest.Items {
		itemType := strings.TrimSpace(item.Type)
		if itemType == "" || itemType == "role_definition" {
			itemType = "role_definition_asset"
		}
		if isLegacyRoleMemoryItemType(itemType) {
			continue
		}
		items = append(items, agent.AgentRoleMaterializationItem{
			Type:        itemType,
			Name:        strings.TrimSpace(item.Name),
			Description: strings.TrimSpace(item.Description),
			Required:    item.Required,
			Action:      strings.TrimSpace(item.Action),
			ResourceID:  strings.TrimSpace(item.ResourceID),
			AccessLevel: strings.TrimSpace(item.AccessLevel),
			Branch:      strings.TrimSpace(item.Branch),
			Content:     item.Content,
		})
	}
	return agent.AgentRoleMaterialization{
		RoleID:               strings.TrimSpace(r.ID),
		RoleVersionID:        strings.TrimSpace(r.RoleVersionID),
		ActiveDefinitionPath: activeDefinitionPath,
		ManifestItems:        items,
	}
}

type meshXRoleRealizationResponse struct {
	RoleID  string                     `json:"roleId"`
	AgentID string                     `json:"agentId"`
	DryRun  bool                       `json:"dryRun"`
	Apply   bool                       `json:"apply"`
	Items   []meshXRoleRealizationItem `json:"items"`
}

func (r meshXRoleRealizationResponse) toResult() RealizationResult {
	items := make([]RealizationItem, 0, len(r.Items))
	for _, item := range r.Items {
		itemType := strings.TrimSpace(item.ManifestItemType)
		if isLegacyRoleMemoryItemType(itemType) {
			continue
		}
		items = append(items, RealizationItem{
			ManifestItemType: itemType,
			ManifestItemName: strings.TrimSpace(item.ManifestItemName),
			Action:           strings.TrimSpace(item.Action),
			Status:           strings.TrimSpace(item.Status),
			ResourceID:       strings.TrimSpace(item.ResourceID),
			AccessLevel:      strings.TrimSpace(item.AccessLevel),
			Branch:           strings.TrimSpace(item.Branch),
			Message:          strings.TrimSpace(item.Message),
			Required:         item.Required,
		})
	}
	return RealizationResult{
		RoleID:  strings.TrimSpace(r.RoleID),
		AgentID: strings.TrimSpace(r.AgentID),
		DryRun:  r.DryRun,
		Apply:   r.Apply,
		Items:   items,
	}
}

func isLegacyRoleMemoryItemType(itemType string) bool {
	return strings.TrimSpace(itemType) == "memory"
}

type meshXRoleRealizationItem struct {
	ManifestItemType string `json:"manifestItemType"`
	ManifestItemName string `json:"manifestItemName,omitempty"`
	Action           string `json:"action"`
	Status           string `json:"status"`
	ResourceID       string `json:"resourceId,omitempty"`
	AccessLevel      string `json:"accessLevel,omitempty"`
	Branch           string `json:"branch,omitempty"`
	Message          string `json:"message,omitempty"`
	Required         bool   `json:"required,omitempty"`
}

func normalizeMeshXBaseURL(raw string) (*url.URL, error) {
	trimmed := strings.TrimRight(strings.TrimSpace(raw), "/")
	if trimmed == "" {
		return nil, errors.New("meshx base URL is required")
	}
	parsed, err := url.Parse(trimmed)
	if err != nil {
		return nil, fmt.Errorf("parse meshx base URL: %w", err)
	}
	if parsed.Scheme != "http" && parsed.Scheme != "https" {
		return nil, errors.New("meshx base URL must use http(s)")
	}
	if parsed.Host == "" {
		return nil, errors.New("meshx base URL must include host")
	}
	return parsed, nil
}

func singleJoiningSlash(a, b string) string {
	aslash := strings.HasSuffix(a, "/")
	bslash := strings.HasPrefix(b, "/")
	switch {
	case aslash && bslash:
		return a + b[1:]
	case !aslash && !bslash:
		return a + "/" + b
	default:
		return a + b
	}
}
