package tools

import (
	"context"
	"strings"

	"github.com/google/uuid"
	"github.com/modelcontextprotocol/go-sdk/mcp"

	"git.warky.dev/wdevs/amcs/internal/config"
	"git.warky.dev/wdevs/amcs/internal/session"
	"git.warky.dev/wdevs/amcs/internal/store"
	thoughttypes "git.warky.dev/wdevs/amcs/internal/types"
)

type LearningsTool struct {
	store    *store.DB
	sessions *session.ActiveProjects
	cfg      config.SearchConfig
}

type AddLearningInput struct {
	Summary               string     `json:"summary" jsonschema:"short curated learning summary"`
	Details               string     `json:"details,omitempty" jsonschema:"optional detailed learning body"`
	Category              string     `json:"category,omitempty"`
	Area                  string     `json:"area,omitempty"`
	Status                string     `json:"status,omitempty"`
	Priority              string     `json:"priority,omitempty"`
	Confidence            string     `json:"confidence,omitempty"`
	ActionRequired        *bool      `json:"action_required,omitempty"`
	SourceType            string     `json:"source_type,omitempty"`
	SourceRef             string     `json:"source_ref,omitempty"`
	Project               string     `json:"project,omitempty" jsonschema:"project name or id; falls back to active session project"`
	RelatedThoughtID      *uuid.UUID `json:"related_thought_id,omitempty"`
	RelatedSkillID        *uuid.UUID `json:"related_skill_id,omitempty"`
	ReviewedBy            *string    `json:"reviewed_by,omitempty"`
	DuplicateOfLearningID *uuid.UUID `json:"duplicate_of_learning_id,omitempty"`
	SupersedesLearningID  *uuid.UUID `json:"supersedes_learning_id,omitempty"`
	Tags                  []string   `json:"tags,omitempty"`
}

type AddLearningOutput struct {
	Learning thoughttypes.Learning `json:"learning"`
}

type GetLearningInput struct {
	ID uuid.UUID `json:"id" jsonschema:"learning id"`
}

type GetLearningOutput struct {
	Learning thoughttypes.Learning `json:"learning"`
}

type ListLearningsInput struct {
	Limit    int    `json:"limit,omitempty"`
	Project  string `json:"project,omitempty" jsonschema:"project name or id; falls back to active session project"`
	Category string `json:"category,omitempty"`
	Area     string `json:"area,omitempty"`
	Status   string `json:"status,omitempty"`
	Priority string `json:"priority,omitempty"`
	Tag      string `json:"tag,omitempty"`
	Query    string `json:"query,omitempty"`
}

type ListLearningsOutput struct {
	Learnings []thoughttypes.Learning `json:"learnings"`
}

func NewLearningsTool(db *store.DB, sessions *session.ActiveProjects, cfg config.SearchConfig) *LearningsTool {
	return &LearningsTool{store: db, sessions: sessions, cfg: cfg}
}

func (t *LearningsTool) Add(ctx context.Context, req *mcp.CallToolRequest, in AddLearningInput) (*mcp.CallToolResult, AddLearningOutput, error) {
	summary := strings.TrimSpace(in.Summary)
	if summary == "" {
		return nil, AddLearningOutput{}, errRequiredField("summary")
	}

	project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, false)
	if err != nil {
		return nil, AddLearningOutput{}, err
	}

	learning := thoughttypes.Learning{
		Summary:               summary,
		Details:               strings.TrimSpace(in.Details),
		Category:              defaultString(strings.TrimSpace(in.Category), "insight"),
		Area:                  defaultString(strings.TrimSpace(in.Area), "other"),
		Status:                thoughttypes.LearningStatus(defaultString(strings.TrimSpace(in.Status), string(thoughttypes.LearningStatusPending))),
		Priority:              thoughttypes.LearningPriority(defaultString(strings.TrimSpace(in.Priority), string(thoughttypes.LearningPriorityMedium))),
		Confidence:            thoughttypes.LearningEvidenceLevel(defaultString(strings.TrimSpace(in.Confidence), string(thoughttypes.LearningEvidenceHypothesis))),
		SourceType:            strings.TrimSpace(in.SourceType),
		SourceRef:             strings.TrimSpace(in.SourceRef),
		RelatedThoughtID:      in.RelatedThoughtID,
		RelatedSkillID:        in.RelatedSkillID,
		ReviewedBy:            in.ReviewedBy,
		DuplicateOfLearningID: in.DuplicateOfLearningID,
		SupersedesLearningID:  in.SupersedesLearningID,
		Tags:                  normalizeStringSlice(in.Tags),
	}
	if in.ActionRequired != nil {
		learning.ActionRequired = *in.ActionRequired
	}
	if project != nil {
		learning.ProjectID = &project.ID
	}

	created, err := t.store.CreateLearning(ctx, learning)
	if err != nil {
		return nil, AddLearningOutput{}, err
	}
	return nil, AddLearningOutput{Learning: created}, nil
}

func (t *LearningsTool) Get(ctx context.Context, _ *mcp.CallToolRequest, in GetLearningInput) (*mcp.CallToolResult, GetLearningOutput, error) {
	learning, err := t.store.GetLearning(ctx, in.ID)
	if err != nil {
		return nil, GetLearningOutput{}, err
	}
	return nil, GetLearningOutput{Learning: learning}, nil
}

func (t *LearningsTool) List(ctx context.Context, req *mcp.CallToolRequest, in ListLearningsInput) (*mcp.CallToolResult, ListLearningsOutput, error) {
	project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, false)
	if err != nil {
		return nil, ListLearningsOutput{}, err
	}

	filter := thoughttypes.LearningFilter{
		Limit:    normalizeLimit(in.Limit, t.cfg),
		Category: strings.TrimSpace(in.Category),
		Area:     strings.TrimSpace(in.Area),
		Status:   strings.TrimSpace(in.Status),
		Priority: strings.TrimSpace(in.Priority),
		Tag:      strings.TrimSpace(in.Tag),
		Query:    strings.TrimSpace(in.Query),
	}
	if project != nil {
		filter.ProjectID = &project.ID
	}

	items, err := t.store.ListLearnings(ctx, filter)
	if err != nil {
		return nil, ListLearningsOutput{}, err
	}
	return nil, ListLearningsOutput{Learnings: items}, nil
}

func defaultString(value string, fallback string) string {
	if value == "" {
		return fallback
	}
	return value
}

func normalizeStringSlice(values []string) []string {
	if len(values) == 0 {
		return []string{}
	}
	out := make([]string, 0, len(values))
	seen := map[string]struct{}{}
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		if _, ok := seen[trimmed]; ok {
			continue
		}
		seen[trimmed] = struct{}{}
		out = append(out, trimmed)
	}
	return out
}
