package exec

import (
	"context"
	"errors"

	invopopjsonschema "github.com/invopop/jsonschema"
	orderedmap "github.com/wk8/go-ordered-map/v2"
)

// Skill Exec exposes the existing Resource Skill API without extending its
// persistence or lifecycle contracts.

// --- existing Skill API projections ---

type skillRepositoryRef struct {
	ID                        string `json:"id"`
	RepoRole                  string `json:"repoRole"`
	RepoOwner                 string `json:"repoOwner"`
	RepoName                  string `json:"repoName"`
	RepoFullName              string `json:"repoFullName"`
	RepoID                    string `json:"repoId,omitempty"`
	DefaultRef                string `json:"defaultRef,omitempty"`
	RepositoryAccessProfile   string `json:"repositoryAccessProfile,omitempty"`
	RepositoryAccessStatus    string `json:"repositoryAccessStatus,omitempty"`
	RepositoryAccessLastError string `json:"repositoryAccessLastError,omitempty"`
	UpdatedAt                 string `json:"updatedAt"`
}

type skillRepositories struct {
	Primary *skillRepositoryRef `json:"canonical,omitempty"`
}

type skill struct {
	ID                string            `json:"id"`
	WorkspaceID       string            `json:"workspaceId"`
	Slug              string            `json:"slug"`
	Name              string            `json:"name"`
	Description       string            `json:"description,omitempty"`
	StorageScope      string            `json:"storageScope"`
	StorageOwnerLogin string            `json:"storageOwnerLogin"`
	VisibilityScope   string            `json:"visibilityScope"`
	VisibilityOwnerID string            `json:"visibilityOwnerId"`
	SkillCount        int               `json:"skillCount"`
	IsActive          bool              `json:"isActive"`
	IsOfficial        bool              `json:"isOfficial"`
	Repositories      skillRepositories `json:"repositories"`
	Role              string            `json:"role,omitempty"`
	CanManage         bool              `json:"canManage"`
	CreatedBy         string            `json:"createdBy,omitempty"`
	UpdatedBy         string            `json:"updatedBy,omitempty"`
	CreatedAt         string            `json:"createdAt"`
	UpdatedAt         string            `json:"updatedAt"`
}

type skillImportedSkill struct {
	Name        string   `json:"name,omitempty"`
	Description string   `json:"description,omitempty"`
	Path        string   `json:"path"`
	Layout      string   `json:"layout,omitempty"`
	SizeBytes   int64    `json:"sizeBytes"`
	Status      string   `json:"status"`
	IssueCode   string   `json:"issueCode,omitempty"`
	Frontmatter []string `json:"frontmatter,omitempty"`
}

type skillImportWarning struct {
	Code       string `json:"code"`
	Message    string `json:"message"`
	Path       string `json:"path,omitempty"`
	SizeBytes  int64  `json:"sizeBytes,omitempty"`
	LimitBytes int64  `json:"limitBytes,omitempty"`
}

// --- request DTOs ---

type skillListRequest struct {
	Query          string `json:"query,omitempty"`
	Page           int    `json:"page,omitempty" jsonschema:"minimum=1"`
	PerPage        int    `json:"perPage,omitempty" jsonschema:"minimum=1,maximum=100"`
	TemplateLocale string `json:"templateLocale,omitempty"`
	View           string `json:"view,omitempty" jsonschema:"enum=created_by_me,enum=official,enum=shared"`
}

type skillGetRequest struct {
	SkillID string `json:"skillId" jsonschema:"minLength=1,pattern=.*\\S.*"`
}

type skillCreateRequest struct {
	Name             string `json:"name" jsonschema:"minLength=1,pattern=.*\\S.*"`
	RepoNameOverride string `json:"repoNameOverride,omitempty"`
	Description      string `json:"description,omitempty"`
	StorageScope     string `json:"storageScope,omitempty"`
	VisibilityScope  string `json:"visibilityScope,omitempty"`
	TemplateLocale   string `json:"templateLocale,omitempty"`
	IsActive         *bool  `json:"isActive,omitempty"`
}

type skillUpdateRequest struct {
	SkillID     string  `json:"skillId"`
	Name        *string `json:"name,omitempty"`
	Description *string `json:"description,omitempty"`
}

// JSONSchema preserves PATCH semantics: skillId plus at least one display
// mutation. Description may be empty to clear it; a supplied name is nonblank.
func (skillUpdateRequest) JSONSchema() *invopopjsonschema.Schema {
	properties := orderedmap.New[string, *invopopjsonschema.Schema]()
	properties.Set("skillId", skillNonBlankStringSchema(0))
	properties.Set("name", skillNonBlankStringSchema(0))
	properties.Set("description", &invopopjsonschema.Schema{Type: "string"})
	minProperties := uint64(2)
	return &invopopjsonschema.Schema{
		Type:                 "object",
		Properties:           properties,
		Required:             []string{"skillId"},
		MinProperties:        &minProperties,
		AdditionalProperties: invopopjsonschema.FalseSchema,
	}
}

type skillImportRequest struct {
	SkillID string `json:"skillId"`
	File    string `json:"file,omitempty"`
	URL     string `json:"url,omitempty"`
}

// JSONSchema requires exactly one import source while keeping the command body
// metadata-only. File content is streamed separately by the Desktop runtime.
func (skillImportRequest) JSONSchema() *invopopjsonschema.Schema {
	properties := orderedmap.New[string, *invopopjsonschema.Schema]()
	properties.Set("skillId", skillNonBlankStringSchema(0))
	properties.Set("file", skillNonBlankStringSchema(0))
	properties.Set("url", skillNonBlankStringSchema(0))
	return &invopopjsonschema.Schema{
		Type:                 "object",
		Properties:           properties,
		Required:             []string{"skillId"},
		AdditionalProperties: invopopjsonschema.FalseSchema,
		OneOf: []*invopopjsonschema.Schema{
			{Required: []string{"file"}},
			{Required: []string{"url"}},
		},
	}
}

type skillDeleteRequest struct {
	RequestID string `json:"requestId" jsonschema:"minLength=1,maxLength=128,pattern=.*\\S.*"`
	SkillID   string `json:"skillId" jsonschema:"minLength=1,pattern=.*\\S.*"`
}

func skillNonBlankStringSchema(maxLength uint64) *invopopjsonschema.Schema {
	minLength := uint64(1)
	schema := &invopopjsonschema.Schema{Type: "string", MinLength: &minLength, Pattern: `.*\S.*`}
	if maxLength > 0 {
		schema.MaxLength = &maxLength
	}
	return schema
}

func skillString(value string) *string { return &value }

// --- result DTOs ---

type skillListResult struct {
	Items   []skill `json:"items"`
	Page    int     `json:"page"`
	PerPage int     `json:"perPage"`
	Total   int64   `json:"total"`
	HasMore bool    `json:"hasMore"`
}

type skillImportResult struct {
	Skill          skill                `json:"skill"`
	ImportedSkills []skillImportedSkill `json:"importedSkills"`
	CommitSHA      string               `json:"commitSha,omitempty"`
	SkillCount     int                  `json:"skillCount"`
	Warnings       []skillImportWarning `json:"warnings,omitempty"`
}

type skillDeleteResult struct {
	ApprovalID string `json:"approvalId"`
	Status     string `json:"status" jsonschema:"enum=pending,enum=rejected,enum=deleted"`
}

// --- typed route boundary ---

type skillRuntime interface {
	list(context.Context, skillListRequest) (skillListResult, error)
	get(context.Context, skillGetRequest) (skill, error)
	create(context.Context, skillCreateRequest) (skill, error)
	update(context.Context, skillUpdateRequest) (skill, error)
	importContent(context.Context, skillImportRequest) (skillImportResult, error)
	delete(context.Context, skillDeleteRequest) (skillDeleteResult, error)
}

type skillHandler struct {
	remote  skillRuntime
	read    remoteReadTransport
	natural naturalMutationTransport
	receipt requestReceiptTransport
}

func (handler skillHandler) list(ctx context.Context, request actionScopedJSON[skillListRequest]) (actionResult[skillListResult], error) {
	result, err := handler.runtime(request.Authorized).list(ctx, request.Body)
	if err != nil {
		return actionResult[skillListResult]{}, classifyAPIError(err, "list Skills failed")
	}
	if result.Items == nil {
		result.Items = []skill{}
	}
	return actionResult[skillListResult]{Data: result, Text: "Listed Skills."}, nil
}

func (handler skillHandler) get(ctx context.Context, request actionScopedJSON[skillGetRequest]) (actionResult[skill], error) {
	result, err := handler.runtime(request.Authorized).get(ctx, request.Body)
	return skillActionResult(result, err, "get Skill failed", "Fetched Skill.")
}

func (handler skillHandler) create(ctx context.Context, request actionScopedJSON[skillCreateRequest]) (actionResult[skill], error) {
	result, err := handler.runtime(request.Authorized).create(ctx, request.Body)
	return skillActionResult(result, err, "create Skill failed", "Created Skill.")
}

func (handler skillHandler) update(ctx context.Context, request actionScopedJSON[skillUpdateRequest]) (actionResult[skill], error) {
	result, err := handler.runtime(request.Authorized).update(ctx, request.Body)
	return skillActionResult(result, err, "update Skill failed", "Updated Skill.")
}

func (handler skillHandler) importContent(ctx context.Context, request actionScopedJSON[skillImportRequest]) (actionResult[skillImportResult], error) {
	result, err := handler.runtime(request.Authorized).importContent(ctx, request.Body)
	if err != nil {
		var typed *Error
		if errors.As(err, &typed) {
			return actionResult[skillImportResult]{}, typed
		}
		return actionResult[skillImportResult]{}, classifyAPIError(err, "import Skill content failed")
	}
	if result.ImportedSkills == nil {
		result.ImportedSkills = []skillImportedSkill{}
	}
	return actionResult[skillImportResult]{Data: result, Text: "Imported Skill content."}, nil
}

func (handler skillHandler) delete(
	ctx context.Context,
	request actionScopedJSON[skillDeleteRequest],
) (actionResult[skillDeleteResult], error) {
	result, err := handler.runtime(request.Authorized).delete(ctx, request.Body)
	if err != nil {
		return actionResult[skillDeleteResult]{},
			classifyAPIError(err, "delete Skill failed")
	}
	text := "Skill deletion is pending approval."
	if result.Status == "rejected" {
		text = "Skill deletion was rejected."
	} else if result.Status == "deleted" {
		text = "Deleted Skill."
	}
	return actionResult[skillDeleteResult]{Data: result, Text: text}, nil
}

func (handler skillHandler) runtime(authorized runtimeAuthorization) skillRuntime {
	if handler.remote != nil {
		return handler.remote
	}
	return skillRemote{client: authorized.Server, workDir: authorized.WorkDir, read: handler.read, natural: handler.natural, receipt: handler.receipt}
}

func skillActionResult(
	result skill,
	err error,
	problem, text string,
) (actionResult[skill], error) {
	if err != nil {
		return actionResult[skill]{}, classifyAPIError(err, problem)
	}
	return actionResult[skill]{Data: result, Text: text}, nil
}

func unavailableSkillAction[O any]() (actionResult[O], error) {
	return actionResult[O]{}, NewError(CodeInternal, "Skill Exec runtime is not configured")
}

// --- action specs ---

var skillListSpec = actionSpec[skillListRequest, skillListResult]{
	Meta: actionMeta{
		Domain:     "skill",
		Name:       "list",
		Summary:    "list Skills visible to the current Human",
		Permission: permissionReadOnly,
	},
	Examples: []actionExample[skillListRequest]{
		{Title: "Skills I created", Input: skillListRequest{View: "created_by_me"}},
	},
}

var skillGetSpec = actionSpec[skillGetRequest, skill]{
	Meta: actionMeta{
		Domain:     "skill",
		Name:       "get",
		Summary:    "get one Skill visible to the current Human",
		Permission: permissionReadOnly,
	},
	Examples: []actionExample[skillGetRequest]{
		{Title: "by id", Input: skillGetRequest{SkillID: "skill_123"}},
	},
}

var skillCreateSpec = actionSpec[skillCreateRequest, skill]{
	Meta: actionMeta{
		Domain:     "skill",
		Name:       "create",
		Summary:    "create a Skill for the current Human",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[skillCreateRequest]{
		{Title: "create Skill", Input: skillCreateRequest{Name: "release-helper"}},
	},
}

var skillUpdateSpec = actionSpec[skillUpdateRequest, skill]{
	Meta: actionMeta{
		Domain:     "skill",
		Name:       "update",
		Summary:    "update a Skill's display fields",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[skillUpdateRequest]{
		{Title: "rename Skill", Input: skillUpdateRequest{
			SkillID: "skill_123", Name: skillString("release-helper"),
		}},
	},
}

var skillImportSpec = actionSpec[skillImportRequest, skillImportResult]{
	Meta: actionMeta{
		Domain:     "skill",
		Name:       "import",
		Summary:    "import Skill content from one local file or URL",
		Permission: permissionAgentScopedMutation,
	},
	Examples: []actionExample[skillImportRequest]{
		{Title: "import from URL", Input: skillImportRequest{
			SkillID: "skill_123", URL: "https://example.test/skills.zip",
		}},
	},
}

var skillDeleteSpec = actionSpec[skillDeleteRequest, skillDeleteResult]{
	Meta: actionMeta{
		Domain:     "skill",
		Name:       "delete",
		Summary:    "request approval, then delete a managed Skill",
		Permission: permissionExistingGate,
	},
	Examples: []actionExample[skillDeleteRequest]{
		{Title: "delete Skill", Input: skillDeleteRequest{
			RequestID: "req-skill-delete-01", SkillID: "skill_123",
		}},
	},
}
