package main

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	appErrors "github.com/nexuscrm/backend/pkg/errors"
	"gopkg.in/yaml.v3"
)

// Simplified Contracts structs (matching api_contracts.json)
type Contract struct {
	Definitions []Definition `json:"definitions"`
}

type Definition struct {
	Name        string  `json:"name"`
	Description string  `json:"description"`
	Type        string  `json:"type"`
	Fields      []Field `json:"fields"`
}

type Field struct {
	Name        string `json:"name"`
	Type        string `json:"type"`
	Required    bool   `json:"required"`
	Description string `json:"description"`
}

// OpenAPI structs
type API struct {
	OpenAPI    string                 `yaml:"openapi"`
	Info       Info                   `yaml:"info"`
	Components Components             `yaml:"components"`
	Paths      map[string]interface{} `yaml:"paths"` // Empty for now, just schemas
}

type Info struct {
	Title       string `yaml:"title"`
	Description string `yaml:"description"`
	Version     string `yaml:"version"`
}

type Components struct {
	Schemas map[string]Schema `yaml:"schemas"`
}

type Schema struct {
	Type        string              `yaml:"type"`
	Description string              `yaml:"description,omitempty"`
	Properties  map[string]Property `yaml:"properties,omitempty"`
	Required    []string            `yaml:"required,omitempty"`
	Items       *Property           `yaml:"items,omitempty"` // For arrays
}

type Property struct {
	Type        string              `yaml:"type,omitempty"`
	Description string              `yaml:"description,omitempty"`
	Ref         string              `yaml:"$ref,omitempty"`
	Items       *Property           `yaml:"items,omitempty"`
	Format      string              `yaml:"format,omitempty"`
	Properties  map[string]Property `yaml:"properties,omitempty"` // For nested objects/maps
}

func generateOpenAPI(projectRoot string) error {
	// 1. Read api_contracts.json
	contractsPath := filepath.Join(projectRoot, "backend/internal/bootstrap/api_contracts.json")
	bytes, err := os.ReadFile(contractsPath)
	if err != nil {
		return appErrors.NewInternalError("failed to read contracts", err)
	}

	var contract Contract
	if err := json.Unmarshal(bytes, &contract); err != nil {
		return appErrors.NewValidationError("api_contracts.json", "failed to parse contracts: "+err.Error())
	}

	// 2. Build OpenAPI structure
	api := API{
		OpenAPI: "3.0.0",
		Info: Info{
			Title:       "NexusCRM API",
			Description: "Strict API Schema generated from single source of truth.",
			Version:     "1.0.0",
		},
		Components: Components{
			Schemas: make(map[string]Schema),
		},
		Paths: make(map[string]interface{}),
	}

	for _, def := range contract.Definitions {
		schema := Schema{
			Type:        "object",
			Description: def.Description,
			Properties:  make(map[string]Property),
			Required:    []string{},
		}

		for _, f := range def.Fields {
			prop := mapType(f.Type)
			prop.Description = f.Description
			schema.Properties[f.Name] = prop

			if f.Required {
				schema.Required = append(schema.Required, f.Name)
			}
		}

		api.Components.Schemas[def.Name] = schema
	}

	// 3. Write openapi.yaml
	outPath := filepath.Join(projectRoot, "docs/openapi.yaml")

	// Ensure docs dir exists
	dir := filepath.Dir(outPath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return appErrors.NewInternalError("failed to create docs directory", err)
	}

	file, err := os.Create(outPath)
	if err != nil {
		return appErrors.NewInternalError("failed to create output file", err)
	}
	defer file.Close()

	encoder := yaml.NewEncoder(file)
	encoder.SetIndent(2)
	defer encoder.Close()

	if err := encoder.Encode(api); err != nil {
		return appErrors.NewInternalError("failed to encode yaml", err)
	}

	fmt.Printf("✅ Generated OpenAPI spec at %s\n", outPath)
	return nil
}

// mapType converts our internal type system to OpenAPI types
func mapType(t string) Property {
	// Handle arrays: "array<Type>"
	if strings.HasPrefix(t, "array<") && strings.HasSuffix(t, ">") {
		inner := t[6 : len(t)-1]
		// Check if inner is map
		if strings.HasPrefix(inner, "map<") {
			return Property{
				Type: "array",
				Items: &Property{
					Type: "object", // simplistic map representation
				},
			}
		}
		// Check if inner is string/int/bool
		if isPrimitive(inner) {
			return Property{
				Type: "array",
				Items: &Property{
					Type: mapPrimitive(inner),
				},
			}
		}
		// Assume ref
		return Property{
			Type: "array",
			Items: &Property{
				Ref: "#/components/schemas/" + inner,
			},
		}
	}

	// Handle maps: "map<K,V>" -> object
	if strings.HasPrefix(t, "map<") {
		return Property{Type: "object"}
	}

	// Handle Primitives
	if isPrimitive(t) {
		p := Property{Type: mapPrimitive(t)}
		if t == "integer" {
			p.Format = "int32"
		}
		return p
	}

	// Handle Struct References
	return Property{
		Ref: "#/components/schemas/" + t,
	}
}

func isPrimitive(t string) bool {
	switch t {
	case "string", "integer", "boolean", "any":
		return true
	}
	return false
}

func mapPrimitive(t string) string {
	switch t {
	case "string":
		return "string"
	case "integer":
		return "integer"
	case "boolean":
		return "boolean"
	case "any":
		return "object" // generic object
	}
	return "string"
}
