package tools

import (
	"context"
	"strings"
	"time"

	"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 PlansTool struct {
	store    *store.DB
	sessions *session.ActiveProjects
	cfg      config.SearchConfig
}

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

// --- I/O types ---

type CreatePlanInput struct {
	Title            string   `json:"title" jsonschema:"plan title"`
	Description      string   `json:"description,omitempty"`
	Status           string   `json:"status,omitempty" jsonschema:"draft|active|blocked|completed|cancelled|superseded"`
	Priority         string   `json:"priority,omitempty" jsonschema:"low|medium|high|critical"`
	Project          string   `json:"project,omitempty" jsonschema:"project name or id; falls back to active session project"`
	Owner            string   `json:"owner,omitempty"`
	DueDate          string   `json:"due_date,omitempty" jsonschema:"RFC3339 timestamp"`
	SupersedesPlanID *int64   `json:"supersedes_plan_id,omitempty"`
	Tags             []string `json:"tags,omitempty"`
}

type CreatePlanOutput struct {
	Plan thoughttypes.Plan `json:"plan"`
}

type GetPlanInput struct {
	ID int64 `json:"id" jsonschema:"plan id"`
}

type GetPlanOutput struct {
	Plan thoughttypes.PlanDetail `json:"plan"`
}

type UpdatePlanInput struct {
	ID                    int64     `json:"id" jsonschema:"plan id"`
	Title                 string    `json:"title,omitempty"`
	Description           string    `json:"description,omitempty"`
	Status                string    `json:"status,omitempty" jsonschema:"draft|active|blocked|completed|cancelled|superseded"`
	Priority              string    `json:"priority,omitempty" jsonschema:"low|medium|high|critical"`
	Owner                 *string   `json:"owner,omitempty" jsonschema:"empty string clears the owner"`
	DueDate               string    `json:"due_date,omitempty" jsonschema:"RFC3339; omit to keep, 'clear' to remove"`
	ClearDueDate          bool      `json:"clear_due_date,omitempty"`
	CompletedAt           string    `json:"completed_at,omitempty" jsonschema:"RFC3339; omit to keep, 'clear' to remove"`
	ClearCompletedAt      bool      `json:"clear_completed_at,omitempty"`
	ReviewedBy            *string   `json:"reviewed_by,omitempty" jsonschema:"empty string clears the reviewer"`
	MarkReviewed          bool      `json:"mark_reviewed,omitempty" jsonschema:"set last_reviewed_at to now"`
	SupersedesPlanID      *int64    `json:"supersedes_plan_id,omitempty"`
	ClearSupersedesPlanID bool      `json:"clear_supersedes_plan_id,omitempty"`
	Tags                  *[]string `json:"tags,omitempty" jsonschema:"replaces all tags when provided; pass [] to clear"`
}

type UpdatePlanOutput struct {
	Plan thoughttypes.Plan `json:"plan"`
}

type DeletePlanInput struct {
	ID int64 `json:"id" jsonschema:"plan id"`
}

type DeletePlanOutput struct {
	Deleted bool `json:"deleted"`
}

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

type ListPlansOutput struct {
	Plans []thoughttypes.Plan `json:"plans"`
}

type PlanDependencyInput struct {
	PlanID          int64 `json:"plan_id" jsonschema:"the plan that depends on another"`
	DependsOnPlanID int64 `json:"depends_on_plan_id" jsonschema:"the plan that must complete first"`
}

type PlanRelatedInput struct {
	PlanAID int64 `json:"plan_a_id"`
	PlanBID int64 `json:"plan_b_id"`
}

type PlanLinkOutput struct {
	OK bool `json:"ok"`
}

type PlanSkillInput struct {
	PlanID  int64 `json:"plan_id"`
	SkillID int64 `json:"skill_id"`
}

type ListPlanSkillsInput struct {
	PlanID int64 `json:"plan_id"`
}

type ListPlanSkillsOutput struct {
	Skills []thoughttypes.AgentSkill `json:"skills"`
}

type PlanGuardrailInput struct {
	PlanID      int64 `json:"plan_id"`
	GuardrailID int64 `json:"guardrail_id"`
}

type ListPlanGuardrailsInput struct {
	PlanID int64 `json:"plan_id"`
}

type ListPlanGuardrailsOutput struct {
	Guardrails []thoughttypes.AgentGuardrail `json:"guardrails"`
}

// --- Handlers ---

func (t *PlansTool) Create(ctx context.Context, req *mcp.CallToolRequest, in CreatePlanInput) (*mcp.CallToolResult, CreatePlanOutput, error) {
	title := strings.TrimSpace(in.Title)
	if title == "" {
		return nil, CreatePlanOutput{}, errRequiredField("title")
	}

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

	plan := thoughttypes.Plan{
		Title:            title,
		Description:      strings.TrimSpace(in.Description),
		Status:           thoughttypes.PlanStatus(defaultString(strings.TrimSpace(in.Status), string(thoughttypes.PlanStatusDraft))),
		Priority:         thoughttypes.PlanPriority(defaultString(strings.TrimSpace(in.Priority), string(thoughttypes.PlanPriorityMedium))),
		Owner:            strings.TrimSpace(in.Owner),
		SupersedesPlanID: in.SupersedesPlanID,
		Tags:             normalizeStringSlice(in.Tags),
	}
	if project != nil {
		plan.ProjectID = &project.NumericID
	}
	if v := strings.TrimSpace(in.DueDate); v != "" {
		t, err := time.Parse(time.RFC3339, v)
		if err != nil {
			return nil, CreatePlanOutput{}, errInvalidField("due_date", "invalid due_date", "use RFC3339 format")
		}
		plan.DueDate = &t
	}

	created, err := t.store.CreatePlan(ctx, plan)
	if err != nil {
		return nil, CreatePlanOutput{}, err
	}
	return nil, CreatePlanOutput{Plan: created}, nil
}

func (t *PlansTool) Get(ctx context.Context, _ *mcp.CallToolRequest, in GetPlanInput) (*mcp.CallToolResult, GetPlanOutput, error) {
	detail, err := t.store.GetPlanDetail(ctx, in.ID)
	if err != nil {
		return nil, GetPlanOutput{}, err
	}
	return nil, GetPlanOutput{Plan: detail}, nil
}

func (t *PlansTool) Update(ctx context.Context, _ *mcp.CallToolRequest, in UpdatePlanInput) (*mcp.CallToolResult, UpdatePlanOutput, error) {
	u := thoughttypes.PlanUpdate{
		ReviewedBy:            in.ReviewedBy,
		MarkReviewed:          in.MarkReviewed,
		ClearDueDate:          in.ClearDueDate,
		ClearCompletedAt:      in.ClearCompletedAt,
		ClearSupersedesPlanID: in.ClearSupersedesPlanID,
		SupersedesPlanID:      in.SupersedesPlanID,
		Tags:                  in.Tags,
		Owner:                 in.Owner,
	}
	if v := strings.TrimSpace(in.Title); v != "" {
		u.Title = &v
	}
	if v := strings.TrimSpace(in.Description); v != "" {
		u.Description = &v
	}
	if v := strings.TrimSpace(in.Status); v != "" {
		u.Status = &v
	}
	if v := strings.TrimSpace(in.Priority); v != "" {
		u.Priority = &v
	}
	if v := strings.TrimSpace(in.DueDate); v != "" && !in.ClearDueDate {
		parsed, err := time.Parse(time.RFC3339, v)
		if err != nil {
			return nil, UpdatePlanOutput{}, errInvalidField("due_date", "invalid due_date", "use RFC3339 format")
		}
		u.DueDate = &parsed
	}
	if v := strings.TrimSpace(in.CompletedAt); v != "" && !in.ClearCompletedAt {
		parsed, err := time.Parse(time.RFC3339, v)
		if err != nil {
			return nil, UpdatePlanOutput{}, errInvalidField("completed_at", "invalid completed_at", "use RFC3339 format")
		}
		u.CompletedAt = &parsed
	}

	plan, err := t.store.UpdatePlan(ctx, in.ID, u)
	if err != nil {
		return nil, UpdatePlanOutput{}, err
	}
	return nil, UpdatePlanOutput{Plan: plan}, nil
}

func (t *PlansTool) Delete(ctx context.Context, _ *mcp.CallToolRequest, in DeletePlanInput) (*mcp.CallToolResult, DeletePlanOutput, error) {
	if err := t.store.DeletePlan(ctx, in.ID); err != nil {
		return nil, DeletePlanOutput{}, err
	}
	return nil, DeletePlanOutput{Deleted: true}, nil
}

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

	filter := thoughttypes.PlanFilter{
		Limit:    normalizeLimit(in.Limit, t.cfg),
		Status:   strings.TrimSpace(in.Status),
		Priority: strings.TrimSpace(in.Priority),
		Owner:    strings.TrimSpace(in.Owner),
		Tag:      strings.TrimSpace(in.Tag),
		Query:    strings.TrimSpace(in.Query),
	}
	if project != nil {
		filter.ProjectID = &project.NumericID
	}

	plans, err := t.store.ListPlans(ctx, filter)
	if err != nil {
		return nil, ListPlansOutput{}, err
	}
	return nil, ListPlansOutput{Plans: plans}, nil
}

func (t *PlansTool) AddDependency(ctx context.Context, _ *mcp.CallToolRequest, in PlanDependencyInput) (*mcp.CallToolResult, PlanLinkOutput, error) {
	if in.PlanID == in.DependsOnPlanID {
		return nil, PlanLinkOutput{}, errInvalidField("depends_on_plan_id", "a plan cannot depend on itself", "use a different plan id")
	}
	if err := t.store.AddPlanDependency(ctx, in.PlanID, in.DependsOnPlanID); err != nil {
		return nil, PlanLinkOutput{}, err
	}
	return nil, PlanLinkOutput{OK: true}, nil
}

func (t *PlansTool) RemoveDependency(ctx context.Context, _ *mcp.CallToolRequest, in PlanDependencyInput) (*mcp.CallToolResult, PlanLinkOutput, error) {
	if err := t.store.RemovePlanDependency(ctx, in.PlanID, in.DependsOnPlanID); err != nil {
		return nil, PlanLinkOutput{}, err
	}
	return nil, PlanLinkOutput{OK: true}, nil
}

func (t *PlansTool) AddRelated(ctx context.Context, _ *mcp.CallToolRequest, in PlanRelatedInput) (*mcp.CallToolResult, PlanLinkOutput, error) {
	if in.PlanAID == in.PlanBID {
		return nil, PlanLinkOutput{}, errInvalidField("plan_b_id", "a plan cannot be related to itself", "use a different plan id")
	}
	if err := t.store.AddRelatedPlan(ctx, in.PlanAID, in.PlanBID); err != nil {
		return nil, PlanLinkOutput{}, err
	}
	return nil, PlanLinkOutput{OK: true}, nil
}

func (t *PlansTool) RemoveRelated(ctx context.Context, _ *mcp.CallToolRequest, in PlanRelatedInput) (*mcp.CallToolResult, PlanLinkOutput, error) {
	if err := t.store.RemoveRelatedPlan(ctx, in.PlanAID, in.PlanBID); err != nil {
		return nil, PlanLinkOutput{}, err
	}
	return nil, PlanLinkOutput{OK: true}, nil
}

func (t *PlansTool) AddSkill(ctx context.Context, _ *mcp.CallToolRequest, in PlanSkillInput) (*mcp.CallToolResult, PlanLinkOutput, error) {
	if err := t.store.AddPlanSkill(ctx, in.PlanID, in.SkillID); err != nil {
		return nil, PlanLinkOutput{}, err
	}
	return nil, PlanLinkOutput{OK: true}, nil
}

func (t *PlansTool) RemoveSkill(ctx context.Context, _ *mcp.CallToolRequest, in PlanSkillInput) (*mcp.CallToolResult, PlanLinkOutput, error) {
	if err := t.store.RemovePlanSkill(ctx, in.PlanID, in.SkillID); err != nil {
		return nil, PlanLinkOutput{}, err
	}
	return nil, PlanLinkOutput{OK: true}, nil
}

func (t *PlansTool) ListSkills(ctx context.Context, _ *mcp.CallToolRequest, in ListPlanSkillsInput) (*mcp.CallToolResult, ListPlanSkillsOutput, error) {
	skills, err := t.store.ListPlanSkills(ctx, in.PlanID)
	if err != nil {
		return nil, ListPlanSkillsOutput{}, err
	}
	if skills == nil {
		skills = []thoughttypes.AgentSkill{}
	}
	return nil, ListPlanSkillsOutput{Skills: skills}, nil
}

func (t *PlansTool) AddGuardrail(ctx context.Context, _ *mcp.CallToolRequest, in PlanGuardrailInput) (*mcp.CallToolResult, PlanLinkOutput, error) {
	if err := t.store.AddPlanGuardrail(ctx, in.PlanID, in.GuardrailID); err != nil {
		return nil, PlanLinkOutput{}, err
	}
	return nil, PlanLinkOutput{OK: true}, nil
}

func (t *PlansTool) RemoveGuardrail(ctx context.Context, _ *mcp.CallToolRequest, in PlanGuardrailInput) (*mcp.CallToolResult, PlanLinkOutput, error) {
	if err := t.store.RemovePlanGuardrail(ctx, in.PlanID, in.GuardrailID); err != nil {
		return nil, PlanLinkOutput{}, err
	}
	return nil, PlanLinkOutput{OK: true}, nil
}

func (t *PlansTool) ListGuardrails(ctx context.Context, _ *mcp.CallToolRequest, in ListPlanGuardrailsInput) (*mcp.CallToolResult, ListPlanGuardrailsOutput, error) {
	guardrails, err := t.store.ListPlanGuardrails(ctx, in.PlanID)
	if err != nil {
		return nil, ListPlanGuardrailsOutput{}, err
	}
	if guardrails == nil {
		guardrails = []thoughttypes.AgentGuardrail{}
	}
	return nil, ListPlanGuardrailsOutput{Guardrails: guardrails}, nil
}
