package stub

import (
	"encoding/json"
	"fmt"
	"os"
	"slices"
	"strings"
	"text/template"
)

// ParseSwaggerFile parses a swagger.json / OpenAPI spec and extracts routes.
// Supports both Swagger 2.0 and OpenAPI 3.x path/method structures.
func ParseSwaggerFile(path string) ([]SwaggerRoute, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read swagger: %w", err)
	}

	var spec swaggerSpec
	if err := json.Unmarshal(data, &spec); err != nil {
		return nil, fmt.Errorf("parse swagger: %w", err)
	}

	return extractRoutes(spec.Paths), nil
}

// swaggerSpec is a minimal representation of a Swagger/OpenAPI spec.
type swaggerSpec struct {
	Paths map[string]map[string]json.RawMessage `json:"paths"`
}

// extractRoutes converts parsed paths into SwaggerRoute entries.
func extractRoutes(paths map[string]map[string]json.RawMessage) []SwaggerRoute {
	var routes []SwaggerRoute

	// Sort paths for deterministic output.
	sortedPaths := make([]string, 0, len(paths))
	for p := range paths {
		sortedPaths = append(sortedPaths, p)
	}
	slices.Sort(sortedPaths)

	for _, path := range sortedPaths {
		methods := paths[path]
		sortedMethods := make([]string, 0, len(methods))
		for m := range methods {
			sortedMethods = append(sortedMethods, m)
		}
		slices.Sort(sortedMethods)

		for _, method := range sortedMethods {
			m := strings.ToUpper(method)
			// Skip non-HTTP entries like "parameters".
			switch m {
			case "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS":
			default:
				continue
			}

			statusCode := defaultStatusCode(m)
			routes = append(routes, SwaggerRoute{
				Method:     m,
				Path:       path,
				StatusCode: statusCode,
				Response:   "{}",
			})
		}
	}
	return routes
}

func defaultStatusCode(method string) int {
	switch method {
	case "POST":
		return 201
	case "DELETE":
		return 204
	default:
		return 200
	}
}

// GenerateSwaggerStub generates Go source files for an HTTP stub server.
// Currently returns empty JSON ({}) with method-appropriate status codes.
// Schema-derived responses (via kin-openapi) are planned for a future iteration.
func GenerateSwaggerStub(cfg *Config, routes []SwaggerRoute) (map[string]string, error) {
	if len(routes) == 0 {
		return nil, fmt.Errorf("no swagger routes to generate stub for %s", cfg.Service)
	}

	port := cfg.HTTPPort
	if port == 0 {
		port = DefaultHTTPPort
	}

	data := swaggerTemplateData{
		Service: cfg.Service,
		Port:    port,
		Routes:  routes,
	}

	funcs := template.FuncMap{
		"goQuote": goQuote,
	}

	tmpl := template.Must(template.New("swagger-main").Funcs(funcs).Parse(swaggerMainTmplText))
	var b strings.Builder
	if err := tmpl.Execute(&b, data); err != nil {
		return nil, fmt.Errorf("execute swagger template: %w", err)
	}

	return map[string]string{"main.go": b.String()}, nil
}

// goQuote returns a Go-safe double-quoted string literal for embedding in generated code.
func goQuote(s string) string {
	return fmt.Sprintf("%q", s)
}

type swaggerTemplateData struct {
	Service string
	Port    int
	Routes  []SwaggerRoute
}

const swaggerMainTmplText = `package main

import (
	"fmt"
	"log"
	"net/http"
)

// Stub HTTP server for {{.Service}}.
// Returns empty JSON ({}) for non-204 endpoints with method-appropriate status codes.

func main() {
	mux := http.NewServeMux()
{{range .Routes}}
	mux.HandleFunc("{{.Method}} {{.Path}}", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader({{.StatusCode}})
		{{- if and (ne .StatusCode 204) (ne .Response "")}}
		fmt.Fprint(w, {{goQuote .Response}})
		{{- end}}
	})
{{end}}
	// Health check.
	mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "{\"status\":\"ok\"}")
	})

	addr := fmt.Sprintf(":%d", {{.Port}})
	log.Printf("{{.Service}} HTTP stub listening on %s", addr)
	if err := http.ListenAndServe(addr, mux); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}
`
