package graph

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"github.com/corezoid/simulator-ai-plugin/plugins/simulator/mcp-server/internal/engines/ecore"
	"github.com/mark3labs/mcp-go/mcp"
	"gopkg.in/yaml.v3"
)

// ---- YAML data model ----

type GraphFile struct {
	LayerID string       `yaml:"layerId"`
	Actors  []GraphActor `yaml:"actors"`
	Edges   []GraphEdge  `yaml:"edges"`
}

type GraphActor struct {
	ID          string                 `yaml:"id"`
	Action      string                 `yaml:"action,omitempty"`
	Title       string                 `yaml:"title"`
	Description string                 `yaml:"description,omitempty"`
	FormID      int                    `yaml:"formId,omitempty"`
	FormName    string                 `yaml:"formName,omitempty"`
	Picture     string                 `yaml:"picture,omitempty"`
	Color       string                 `yaml:"color,omitempty"`
	Data        map[string]interface{} `yaml:"data,omitempty"`
	Position    struct {
		X int `yaml:"x"`
		Y int `yaml:"y"`
	} `yaml:"position"`
}

type GraphEdge struct {
	Source      string `yaml:"source"`
	Target      string `yaml:"target"`
	Action      string `yaml:"action,omitempty"`
	SourceTitle string `yaml:"sourceTitle,omitempty"`
	TargetTitle string `yaml:"targetTitle,omitempty"`
}

// ---- Server response types ----

type layerActor struct {
	ID          string                 `json:"id"`
	Title       string                 `json:"title"`
	Description string                 `json:"description"`
	Color       string                 `json:"color"`
	FormID      int                    `json:"formId"`
	Picture     string                 `json:"picture"`
	LaID        int                    `json:"laId"`
	Data        map[string]interface{} `json:"data"`
	Position    struct {
		X int `json:"x"`
		Y int `json:"y"`
	} `json:"position"`
}

// formIDFromLayerActor returns the formId to use for API calls.
// Prefers the id found in data keys like "__form__408962:view" over the top-level formId.
func formIDFromLayerActor(sa layerActor) int {
	for key := range sa.Data {
		if strings.HasPrefix(key, "__form__") {
			rest := strings.TrimPrefix(key, "__form__")
			if idx := strings.Index(rest, ":"); idx > 0 {
				if id, err := strconv.Atoi(rest[:idx]); err == nil && id > 0 {
					return id
				}
			}
		}
	}
	return sa.FormID
}

type layerEdge struct {
	ID         string `json:"id"`
	Source     string `json:"source"`
	Target     string `json:"target"`
	LaID       int    `json:"laId"`
	LaIDSource int    `json:"laIdSource"`
	LaIDTarget int    `json:"laIdTarget"`
}

// ---- Layer fetch helpers ----

func fetchLayerActors(ctx context.Context, layerID string) ([]layerActor, error) {
	base := ecore.BuildBaseURLForContext(ctx)
	var all []layerActor
	limit, offset := 50, 0
	for {
		u := fmt.Sprintf("%s/graph_layers/paginated/%s?type=nodes&limit=%d&offset=%d", base, ecore.Seg(layerID), limit, offset)
		body, err := ecore.PapiGET(ctx, u)
		if err != nil {
			return nil, err
		}
		var page struct {
			Data []layerActor `json:"data"`
		}
		if err := json.Unmarshal(body, &page); err != nil {
			return nil, fmt.Errorf("parse layer actors: %w (body: %.200s)", err, body)
		}
		all = append(all, page.Data...)
		if len(page.Data) < limit {
			break
		}
		offset += limit
	}
	return all, nil
}

func fetchLayerEdges(ctx context.Context, layerID string) ([]layerEdge, error) {
	base := ecore.BuildBaseURLForContext(ctx)
	var all []layerEdge
	limit, offset := 50, 0
	for {
		u := fmt.Sprintf("%s/graph_layers/paginated/%s?type=edges&limit=%d&offset=%d", base, ecore.Seg(layerID), limit, offset)
		body, err := ecore.PapiGET(ctx, u)
		if err != nil {
			return nil, err
		}
		var page struct {
			Data []layerEdge `json:"data"`
		}
		if err := json.Unmarshal(body, &page); err != nil {
			return nil, fmt.Errorf("parse layer edges: %w (body: %.200s)", err, body)
		}
		all = append(all, page.Data...)
		if len(page.Data) < limit {
			break
		}
		offset += limit
	}
	return all, nil
}

// manageLayerItem is a single create/delete action for the manageLayer API.
type manageLayerItem struct {
	Action string `json:"action"`
	Data   struct {
		ID       string `json:"id"`
		Type     string `json:"type"`
		LaID     int    `json:"laId,omitempty"`
		LaIDSrc  int    `json:"laIdSource,omitempty"`
		LaIDTgt  int    `json:"laIdTarget,omitempty"`
		Position struct {
			X int `json:"x"`
			Y int `json:"y"`
		} `json:"position"`
	} `json:"data"`
}

// ---- Main handlers ----

func handlePushGraphFile(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	if authResult := ecore.EnsureAuth(ctx); authResult != nil {
		return authResult, nil
	}

	args := req.GetArguments()
	layerID, _ := args["layerId"].(string)
	if layerID == "" {
		return mcp.NewToolResultError("[Error] layerId is required"), nil
	}
	if r := ecore.RequireUUID("layerId", layerID); r != nil {
		return r, nil
	}
	filePath := ecore.ResolvePath(layerID + ".yaml")

	rawData, err := os.ReadFile(filePath)
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("[Error] cannot read file %s: %v", filePath, err)), nil
	}

	var graph GraphFile
	if parseErr := yaml.Unmarshal(rawData, &graph); parseErr != nil {
		return mcp.NewToolResultError(fmt.Sprintf("[Error] cannot parse YAML %s: %v", filePath, parseErr)), nil
	}

	result, syncErr := PushGraphFile(graph, ecore.WorkspaceIDForContext(ctx), layerID, ecore.AuthHeaderForContext(ctx), ecore.BuildBaseURLForContext(ctx))
	if syncErr != nil {
		return mcp.NewToolResultError(fmt.Sprintf("[Error] %v", syncErr)), nil
	}

	updatedYAML, marshalErr := yaml.Marshal(&result.UpdatedGraph)
	if marshalErr != nil {
		return mcp.NewToolResultError(fmt.Sprintf("[Error] marshal YAML: %v", marshalErr)), nil
	}
	if writeErr := os.WriteFile(filePath, updatedYAML, 0600); writeErr != nil {
		return mcp.NewToolResultError(fmt.Sprintf("[Error] write YAML: %v", writeErr)), nil
	}

	out, _ := json.Marshal(map[string]interface{}{
		"layerId": result.LayerID,
		"actors": map[string]int{
			"created":   result.ActorsCreated,
			"updated":   result.ActorsUpdated,
			"unchanged": result.ActorsUnchanged,
			"deleted":   result.ActorsDeleted,
			"recreated": result.ActorsRecreated,
		},
		"edges": map[string]int{
			"created": result.EdgesCreated,
			"deleted": result.EdgesDeleted,
		},
		"fileUpdated": true,
	})
	return mcp.NewToolResultText(string(out)), nil
}

// handlePullGraphFile fetches all actors and edges from a layer and writes
// them to <SIMULATOR_WORK_DIR>/<layerId>.yaml (falling back to cwd when the
// env var is unset — see ecore.WorkDir).
func handlePullGraphFile(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	if authResult := ecore.EnsureAuth(ctx); authResult != nil {
		return authResult, nil
	}

	args := req.GetArguments()
	layerID, _ := args["layerId"].(string)
	if layerID == "" {
		return mcp.NewToolResultError("[Error] layerId is required"), nil
	}
	if r := ecore.RequireUUID("layerId", layerID); r != nil {
		return r, nil
	}
	filePath := ecore.ResolvePath(layerID + ".yaml")

	// Fetch actors
	serverActors, err := fetchLayerActors(ctx, layerID)
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("[Error] fetch layer actors: %v", err)), nil
	}

	// Fetch edges
	serverEdges, err := fetchLayerEdges(ctx, layerID)
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("[Error] fetch layer edges: %v", err)), nil
	}

	// Build actor title lookup for edge sourceTitle / targetTitle
	titleByUUID := make(map[string]string, len(serverActors))
	for _, a := range serverActors {
		titleByUUID[a.ID] = a.Title
	}

	// Build GraphFile
	graph := GraphFile{LayerID: layerID}

	// Pre-load sys forms so resolveFormIDToName works (best-effort, non-fatal).
	if _, err := loadSysForms(); err != nil {
		log.Printf("Warning: exportGraph loadSysForms: %v", err)
	}

	for _, sa := range serverActors {
		var ga GraphActor
		ga.ID = sa.ID
		ga.Title = sa.Title
		ga.Description = sa.Description
		ga.Color = sa.Color
		ga.Picture = sa.Picture
		ga.FormID = formIDFromLayerActor(sa)
		if name := resolveFormIDToName(ga.FormID); name != "" {
			ga.FormName = name
		}
		ga.Position.X = sa.Position.X
		ga.Position.Y = sa.Position.Y
		graph.Actors = append(graph.Actors, ga)
	}

	for _, se := range serverEdges {
		graph.Edges = append(graph.Edges, GraphEdge{
			Source:      se.Source,
			Target:      se.Target,
			SourceTitle: titleByUUID[se.Source],
			TargetTitle: titleByUUID[se.Target],
		})
	}

	data, err := yaml.Marshal(&graph)
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("[Error] marshal YAML: %v", err)), nil
	}
	if err := os.WriteFile(filePath, data, 0600); err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("[Error] write file: %v", err)), nil
	}

	out, _ := json.Marshal(map[string]interface{}{
		"layerId":  layerID,
		"filePath": filePath,
		"actors":   len(graph.Actors),
		"edges":    len(graph.Edges),
	})
	return mcp.NewToolResultText(string(out)), nil
}
