package exec

import (
	"encoding/json"
	"fmt"
	"strings"
)

const openAPIVersion = "3.1.0"

// openAPIDocument projects descriptor-only contracts into an OpenAPI 3.1
// artifact. It is documentation, never an execution registry.
type openAPIDocument struct {
	OpenAPI    string                     `json:"openapi"`
	Info       openAPIInfo                `json:"info"`
	Paths      map[string]openAPIPathItem `json:"paths"`
	Components openAPIComponents          `json:"components"`
}

type openAPIInfo struct {
	Title   string `json:"title"`
	Version string `json:"version"`
}

type openAPIPathItem struct {
	Post openAPIOperation `json:"post"`
}

type openAPIOperation struct {
	OperationID string                     `json:"operationId"`
	Summary     string                     `json:"summary"`
	Description string                     `json:"description,omitempty"`
	RequestBody openAPIRequestBody         `json:"requestBody"`
	Responses   map[string]openAPIResponse `json:"responses"`
}

type openAPIRequestBody struct {
	Required bool                        `json:"required"`
	Content  map[string]openAPIMediaType `json:"content"`
}

type openAPIResponse struct {
	Description string                      `json:"description"`
	Content     map[string]openAPIMediaType `json:"content,omitempty"`
}

type openAPIMediaType struct {
	Schema   openAPISchemaRef          `json:"schema"`
	Examples map[string]openAPIExample `json:"examples,omitempty"`
}

type openAPISchemaRef struct {
	Ref string `json:"$ref"`
}

type openAPIExample struct {
	Summary string          `json:"summary,omitempty"`
	Value   json.RawMessage `json:"value"`
}

type openAPIComponents struct {
	Schemas map[string]json.RawMessage `json:"schemas"`
}

type actionErrorResponse struct {
	Error ErrorEnvelope `json:"error"`
}

func (c *Catalog) openAPI() (openAPIDocument, error) {
	if c == nil || len(c.actions) == 0 {
		return openAPIDocument{}, fmt.Errorf("exec: descriptor catalog is empty")
	}
	errorSchema, err := reflectContractSchema(contractType[actionErrorResponse]())
	if err != nil {
		return openAPIDocument{}, fmt.Errorf("exec: error response schema: %w", err)
	}
	document := openAPIDocument{
		OpenAPI: openAPIVersion,
		Info: openAPIInfo{
			Title:   "AgentTeam Exec Actions",
			Version: protocolVersion,
		},
		Paths: make(map[string]openAPIPathItem, len(c.actions)),
		Components: openAPIComponents{
			Schemas: map[string]json.RawMessage{
				"ActionError": errorSchema,
			},
		},
	}
	for _, descriptor := range c.actions {
		path := "/" + descriptor.Domain + "/" + descriptor.Name
		if _, duplicate := document.Paths[path]; duplicate {
			return openAPIDocument{}, fmt.Errorf("exec: duplicate OpenAPI path %q", path)
		}
		document.Components.Schemas[descriptor.InputSchemaName] = append(json.RawMessage(nil), descriptor.InputSchema...)
		document.Components.Schemas[descriptor.OutputSchemaName] = append(json.RawMessage(nil), descriptor.OutputSchema...)
		examples := make(map[string]openAPIExample, len(descriptor.Examples))
		for index, example := range descriptor.Examples {
			name := fmt.Sprintf("example%d", index+1)
			examples[name] = openAPIExample{
				Summary: example.Title,
				Value:   append(json.RawMessage(nil), example.Input...),
			}
		}
		document.Paths[path] = openAPIPathItem{Post: openAPIOperation{
			OperationID: strings.ReplaceAll(descriptor.Domain, "-", "_") + "_" + strings.ReplaceAll(descriptor.Name, "-", "_"),
			Summary:     descriptor.Summary,
			Description: descriptor.Description,
			RequestBody: openAPIRequestBody{
				Required: true,
				Content: map[string]openAPIMediaType{
					"application/json": {
						Schema:   openAPISchemaRef{Ref: "#/components/schemas/" + descriptor.InputSchemaName},
						Examples: examples,
					},
				},
			},
			Responses: map[string]openAPIResponse{
				"200": {
					Description: "Action completed.",
					Content: map[string]openAPIMediaType{
						"application/json": {
							Schema: openAPISchemaRef{Ref: "#/components/schemas/" + descriptor.OutputSchemaName},
						},
					},
				},
				"default": {
					Description: "Action failed.",
					Content: map[string]openAPIMediaType{
						"application/json": {
							Schema: openAPISchemaRef{Ref: "#/components/schemas/ActionError"},
						},
					},
				},
			},
		}}
	}
	return document, nil
}
