package handler

import (
	"reflect"

	"github.com/gorilla/mux"
	"github.com/swaggest/jsonschema-go"
	openapigo "github.com/swaggest/openapi-go"
	"github.com/swaggest/rest/openapi"
)

// OpenAPIExample is a named example for an OpenAPI operation.
type OpenAPIExample struct {
	Name        string
	Summary     string
	Description string
	Value       any
}

// Def is the definition of an OpenAPI operation.
type OpenAPIDef struct {
	ID                  string
	Tags                []string
	Summary             string
	Description         string
	Request             any
	RequestQuery        any
	RequestContentType  string
	RequestExamples     []OpenAPIExample
	Response            any
	ResponseContentType string
	SuccessStatusCode   int
	ErrorStatusCodes    []int
	Deprecated          bool
	SecuritySchemes     []OpenAPISecurityScheme
}

type OpenAPISecurityScheme struct {
	Name   string
	Scopes []string
}

// OpenAPICollector is a collector for OpenAPI operations.
type OpenAPICollector struct {
	collector *openapi.Collector
}

func NewOpenAPICollector(reflector openapigo.Reflector) *OpenAPICollector {
	c := openapi.NewCollector(reflector)

	return &OpenAPICollector{
		collector: c,
	}
}

func (c *OpenAPICollector) Walker(route *mux.Route, _ *mux.Router, _ []*mux.Route) error {
	httpHandler := route.GetHandler()

	if httpHandler == nil {
		return nil
	}

	path, err := route.GetPathTemplate()
	if err != nil && path == "" {
		// If there is no path, skip the route
		return nil
	}

	methods, err := route.GetMethods()
	if err != nil {
		// If there is no methods, skip the route
		return nil
	}

	if handler, ok := httpHandler.(Handler); ok {
		for _, method := range methods {
			if err := c.collector.CollectOperation(method, path, c.collect(method, path, handler.ServeOpenAPI)); err != nil {
				return err
			}
		}
		return nil
	}

	return nil
}

func (c *OpenAPICollector) collect(method string, path string, serveOpenAPIFunc ServeOpenAPIFunc) func(oc openapigo.OperationContext) error {
	return func(oc openapigo.OperationContext) error {
		// Serve the OpenAPI documentation for the handler
		serveOpenAPIFunc(oc)

		// If the handler has annotations, skip the collection
		if c.collector.HasAnnotation(method, path) {
			return nil
		}

		// Automatically sanitize the method and path
		_, _, pathItems, err := openapigo.SanitizeMethodPath(method, path)
		if err != nil {
			return err
		}

		// If there are path items, add them to the request structure
		if len(pathItems) > 0 {
			req := jsonschema.Struct{}
			for _, p := range pathItems {
				req.Fields = append(req.Fields, jsonschema.Field{
					Name:  "F" + p,
					Tag:   reflect.StructTag(`path:"` + p + `"`),
					Value: "",
				})
			}

			oc.AddReqStructure(req)
		}

		return nil
	}
}
