package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"gopkg.in/yaml.v2"
)

// ServiceConfig represents the main configuration structure
type ServiceConfig struct {
	Services map[string]Service `yaml:"services"`
	Global   GlobalConfig       `yaml:"global,omitempty"`
}

// GlobalConfig contains global configuration
type GlobalConfig struct {
	ModuleName    string `yaml:"module_name"`
	OutputDir     string `yaml:"output_dir"`
	PackagePrefix string `yaml:"package_prefix"`
	EnableSwagger bool   `yaml:"enable_swagger"`
	EnableLogging bool   `yaml:"enable_logging"`
	EnableMetrics bool   `yaml:"enable_metrics"`
}

// Service represents individual service configuration
type Service struct {
	Name         string              `yaml:"name"`
	Category     string              `yaml:"category"`
	Package      string              `yaml:"package"`
	Description  string              `yaml:"description"`
	BaseURL      string              `yaml:"base_url"`
	Timeout      int                 `yaml:"timeout"`
	RetryCount   int                 `yaml:"retry_count"`
	Endpoints    map[string]Endpoint `yaml:"endpoints"`
	Middleware   []string            `yaml:"middleware,omitempty"`   // FIXED: Changed to []string
	Dependencies []string            `yaml:"dependencies,omitempty"` // FIXED: Changed to []string
}

// Endpoint represents endpoint configuration
type Endpoint struct {
	Methods       []string          `yaml:"methods"`
	GetPath       string            `yaml:"get_path,omitempty"`
	PostPath      string            `yaml:"post_path,omitempty"`
	PutPath       string            `yaml:"put_path,omitempty"`
	DeletePath    string            `yaml:"delete_path,omitempty"`
	PatchPath     string            `yaml:"patch_path,omitempty"`
	Model         string            `yaml:"model"`
	ResponseModel string            `yaml:"response_model"`
	Description   string            `yaml:"description"`
	Summary       string            `yaml:"summary"`
	Tags          []string          `yaml:"tags"`
	RequireAuth   bool              `yaml:"require_auth"`
	RateLimit     int               `yaml:"rate_limit,omitempty"`
	CacheEnabled  bool              `yaml:"cache_enabled"`
	CacheTTL      int               `yaml:"cache_ttl,omitempty"`
	CustomHeaders map[string]string `yaml:"custom_headers,omitempty"` // ADDED: Missing field
}

// TemplateData holds data for generating handlers
type TemplateData struct {
	ServiceName  string
	ServiceLower string
	ServiceUpper string
	Category     string
	Package      string
	Description  string
	BaseURL      string
	Timeout      int
	RetryCount   int
	Endpoints    []EndpointData
	Timestamp    string
	ModuleName   string
	HasValidator bool
	HasLogger    bool
	HasMetrics   bool
	HasSwagger   bool
	HasAuth      bool
	HasCache     bool
	Dependencies []string // FIXED: Changed to []string
	Middleware   []string // FIXED: Changed to []string
	GlobalConfig GlobalConfig
}

// EndpointData represents processed endpoint data
type EndpointData struct {
	Name           string
	NameLower      string
	NameUpper      string
	NameCamel      string
	Methods        []string
	GetPath        string
	PostPath       string
	PutPath        string
	DeletePath     string
	PatchPath      string
	Model          string
	ResponseModel  string
	Description    string
	Summary        string
	Tags           []string
	HasGet         bool
	HasPost        bool
	HasPut         bool
	HasDelete      bool
	HasPatch       bool
	RequireAuth    bool
	RateLimit      int
	CacheEnabled   bool
	CacheTTL       int
	PathParams     []string
	QueryParams    []string
	RequiredFields []string
	OptionalFields []string
	CustomHeaders  map[string]string
}

// Template remains the same as before...
const handlerTemplate = `
// Code generated by generate-dynamic-handler.go; DO NOT EDIT.
// Generated at: {{.Timestamp}}
// Service: {{.ServiceName}} ({{.Category}})
// Description: {{.Description}}

package handlers

import (
	"context"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"{{.ModuleName}}/internal/config"
	"{{.ModuleName}}/internal/models/reference"
	"{{.ModuleName}}/internal/services/bpjs"
	"{{.ModuleName}}/pkg/logger"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"github.com/google/uuid"
)

// {{.ServiceName}}Handler handles {{.ServiceName}} BPJS services
type {{.ServiceName}}Handler struct {
	service   services.{{.ServiceName}}Service
	validator *validator.Validate
	logger    logger.Logger
	config    config.BpjsConfig
}

// {{.ServiceName}}HandlerConfig contains configuration for {{.ServiceName}}Handler
type {{.ServiceName}}HandlerConfig struct {
	BpjsConfig config.BpjsConfig
	Logger     logger.Logger
	Validator  *validator.Validate
}

// New{{.ServiceName}}Handler creates a new {{.ServiceName}}Handler
func New{{.ServiceName}}Handler(cfg {{.ServiceName}}HandlerConfig) *{{.ServiceName}}Handler {
	return &{{.ServiceName}}Handler{
		service:   services.NewService(cfg.BpjsConfig),
		validator: cfg.Validator,
		logger:    cfg.Logger,
		config:    cfg.BpjsConfig,
	}
}

{{range .Endpoints}}
{{if .HasGet}}
// Get{{.NameUpper}} retrieves {{.Name}} data
{{if $.HasSwagger}}
// @Summary Get {{.Name}} data
// @Description {{.Description}}
// @Tags {{join .Tags ","}}
// @Accept json
// @Produce json
{{if .RequireAuth}}
// @Security ApiKeyAuth
{{end}}
{{range .PathParams}}
// @Param {{.}} path string true "{{.}}"
{{end}}
// @Success 200 {object} reference.{{.ResponseModel}}
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router {{.GetPath}} [get]
{{end}}
func (h *{{$.ServiceName}}Handler) Get{{.NameUpper}}(c *gin.Context) {
	ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second)
	defer cancel()

	// Generate request ID if not present
	requestID := c.GetHeader("X-Request-ID")
	if requestID == "" {
		requestID = uuid.New().String()
		c.Header("X-Request-ID", requestID)
	}

	{{if $.HasLogger}}
	h.logger.Info("Processing Get{{.Name}} request", map[string]interface{}{
		"request_id": requestID,
		"endpoint":   "{{.GetPath}}",
		{{range .PathParams}}
		"{{.}}": c.Param("{{.}}"),
		{{end}}
	})
	{{end}}

	// Extract path parameters
	{{range .PathParams}}
	{{.}} := c.Param("{{.}}")
	if {{.}} == "" {
		{{if $.HasLogger}}
		h.logger.Error("Missing required parameter {{.}}", map[string]interface{}{
			"request_id": requestID,
		})
		{{end}}
		c.JSON(http.StatusBadRequest, reference.ErrorResponse{
			Status:    "error",
			Message:   "Missing required parameter {{.}}",
			RequestID: requestID,
		})
		return
	}
	{{end}}

	// Call service method
	var response reference.{{.ResponseModel}}
	{{if .PathParams}}
	result, err := h.service.Get{{.NameUpper}}(ctx{{range .PathParams}}, {{.}}{{end}})
	{{else}}
	result, err := h.service.Get{{.NameUpper}}(ctx)
	{{end}}
	if err != nil {
		{{if $.HasLogger}}
		h.logger.Error("Failed to get {{.Name}}", map[string]interface{}{
			"error":      err.Error(),
			"request_id": requestID,
		})
		{{end}}
		c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
			Status:    "error",
			Message:   "Internal server error",
			RequestID: requestID,
		})
		return
	}

	// Ensure response has proper fields
	response.Status = "success"
	response.RequestID = requestID
	c.JSON(http.StatusOK, response)
}
{{end}}

{{if .HasPost}}
// Create{{.NameUpper}} creates new {{.Name}}
{{if $.HasSwagger}}
// @Summary Create {{.Name}}
// @Description {{.Description}}
// @Tags {{join .Tags ","}}
// @Accept json
// @Produce json
{{if .RequireAuth}}
// @Security ApiKeyAuth
{{end}}
// @Param request body reference.{{.Model}} true "{{.Name}} data"
// @Success 201 {object} reference.{{.ResponseModel}}
// @Failure 400 {object} reference.ErrorResponse
// @Failure 500 {object} reference.ErrorResponse
// @Router {{.PostPath}} [post]
{{end}}
func (h *{{$.ServiceName}}Handler) Create{{.NameUpper}}(c *gin.Context) {
	ctx, cancel := context.WithTimeout(c.Request.Context(), {{$.Timeout}}*time.Second)
	defer cancel()

	requestID := c.GetHeader("X-Request-ID")
	if requestID == "" {
		requestID = uuid.New().String()
		c.Header("X-Request-ID", requestID)
	}

	{{if $.HasLogger}}
	h.logger.Info("Processing Create{{.Name}} request", map[string]interface{}{
		"request_id": requestID,
	})
	{{end}}

	var req reference.{{.Model}}
	if err := c.ShouldBindJSON(&req); err != nil {
		{{if $.HasLogger}}
		h.logger.Error("Invalid request body", map[string]interface{}{
			"error":      err.Error(),
			"request_id": requestID,
		})
		{{end}}
		c.JSON(http.StatusBadRequest, reference.ErrorResponse{
			Status:    "error",
			Message:   "Invalid request body: " + err.Error(),
			RequestID: requestID,
		})
		return
	}

	// Validate request
	if err := h.validator.Struct(&req); err != nil {
		{{if $.HasLogger}}
		h.logger.Error("Validation failed", map[string]interface{}{
			"error":      err.Error(),
			"request_id": requestID,
		})
		{{end}}
		c.JSON(http.StatusBadRequest, reference.ErrorResponse{
			Status:    "error",
			Message:   "Validation failed: " + err.Error(),
			RequestID: requestID,
		})
		return
	}

	// Call service method
	var response reference.{{.ResponseModel}}
	result, err := h.service.Create{{.NameUpper}}(ctx, &req)
	if err != nil {
		{{if $.HasLogger}}
		h.logger.Error("Failed to create {{.Name}}", map[string]interface{}{
			"error":      err.Error(),
			"request_id": requestID,
		})
		{{end}}
		c.JSON(http.StatusInternalServerError, reference.ErrorResponse{
			Status:    "error",
			Message:   "Internal server error",
			RequestID: requestID,
		})
		return
	}

	// Ensure response has proper fields
	response.Status = "success"
	response.RequestID = requestID
	c.JSON(http.StatusCreated, response)
}
{{end}}
{{end}}
`

func main() {
	if len(os.Args) < 2 {
		printUsage()
		os.Exit(1)
	}

	configFile := os.Args[1]
	var targetService string
	if len(os.Args) > 2 {
		targetService = os.Args[2]
	}

	// Load configuration
	config, err := loadConfig(configFile)
	if err != nil {
		fmt.Printf("❌ Error loading config: %v\n", err)
		os.Exit(1)
	}

	fmt.Println("🚀 Starting BPJS Dynamic Handler Generation...")
	fmt.Printf("📁 Config file: %s\n", configFile)
	if targetService != "" {
		fmt.Printf("🎯 Target service: %s\n", targetService)
	}

	generated := 0
	errors := 0

	// Generate handlers
	for serviceName, service := range config.Services {
		if targetService != "" && serviceName != targetService {
			continue
		}

		fmt.Printf("🔧 Generating handler for service: %s (%s)\n", service.Name, service.Category)
		err := generateHandler(serviceName, service, config.Global)
		if err != nil {
			fmt.Printf("❌ Error generating handler for %s: %v\n", serviceName, err)
			errors++
			continue
		}
		fmt.Printf("✅ Generated handler: %s_handler.go\n", strings.ToLower(serviceName))
		generated++
	}

	// Summary
	fmt.Println("\n📊 Generation Summary:")
	fmt.Printf("   ✅ Successfully generated: %d handlers\n", generated)
	if errors > 0 {
		fmt.Printf("   ❌ Failed: %d handlers\n", errors)
	}

	if generated > 0 {
		fmt.Println("🎉 Generation completed successfully!")
	}
}

func printUsage() {
	fmt.Println("BPJS Dynamic Handler Generator")
	fmt.Println()
	fmt.Println("Usage:")
	fmt.Println("  go run generate-dynamic-handler.go <config-file> [service-name]")
	fmt.Println()
	fmt.Println("Examples:")
	fmt.Println("  go run generate-dynamic-handler.go services-config.yaml")
	fmt.Println("  go run generate-dynamic-handler.go services-config.yaml vclaim")
}

func loadConfig(filename string) (*ServiceConfig, error) {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, fmt.Errorf("failed to read config file: %w", err)
	}

	var config ServiceConfig
	err = yaml.Unmarshal(data, &config)
	if err != nil {
		return nil, fmt.Errorf("failed to parse YAML config: %w", err)
	}

	// Set default values
	if config.Global.ModuleName == "" {
		config.Global.ModuleName = "api-service"
	}
	if config.Global.OutputDir == "" {
		config.Global.OutputDir = "internal/handlers"
	}

	return &config, nil
}

func generateHandler(serviceName string, service Service, globalConfig GlobalConfig) error {
	// Prepare template data
	templateData := TemplateData{
		ServiceName:  service.Name,
		ServiceLower: strings.ToLower(service.Name),
		ServiceUpper: strings.ToUpper(service.Name),
		Category:     service.Category,
		Package:      service.Package,
		Description:  service.Description,
		BaseURL:      service.BaseURL,
		Timeout:      getOrDefault(service.Timeout, 30),
		RetryCount:   getOrDefault(service.RetryCount, 3),
		Timestamp:    time.Now().Format("2006-01-02 15:04:05"),
		ModuleName:   globalConfig.ModuleName,
		HasValidator: true,
		HasLogger:    globalConfig.EnableLogging,
		HasMetrics:   globalConfig.EnableMetrics,
		HasSwagger:   globalConfig.EnableSwagger,
		Dependencies: service.Dependencies, // Now []string
		Middleware:   service.Middleware,   // Now []string
		GlobalConfig: globalConfig,
	}

	// Check for advanced features
	for _, endpoint := range service.Endpoints {
		if endpoint.RequireAuth {
			templateData.HasAuth = true
		}
		if endpoint.CacheEnabled {
			templateData.HasCache = true
		}
	}

	// Process endpoints
	for endpointName, endpoint := range service.Endpoints {
		endpointData := processEndpoint(endpointName, endpoint)
		templateData.Endpoints = append(templateData.Endpoints, endpointData)
	}

	// Create output directory
	outputDir := globalConfig.OutputDir
	if outputDir == "" {
		outputDir = "internal/handlers"
	}
	if err := os.MkdirAll(outputDir, 0755); err != nil {
		return fmt.Errorf("failed to create output directory: %w", err)
	}

	// Generate handler file
	filename := filepath.Join(outputDir, fmt.Sprintf("%s_handler.go", strings.ToLower(serviceName)))

	// Create template with custom functions
	tmpl := template.New("handler").Funcs(template.FuncMap{
		"contains":   strings.Contains,
		"join":       strings.Join,
		"title":      strings.Title,
		"trimPrefix": strings.TrimPrefix,
	})

	tmpl, err := tmpl.Parse(handlerTemplate)
	if err != nil {
		return fmt.Errorf("failed to parse template: %w", err)
	}

	file, err := os.Create(filename)
	if err != nil {
		return fmt.Errorf("failed to create file: %w", err)
	}
	defer file.Close()

	err = tmpl.Execute(file, templateData)
	if err != nil {
		return fmt.Errorf("failed to execute template: %w", err)
	}

	return nil
}

func processEndpoint(name string, endpoint Endpoint) EndpointData {
	data := EndpointData{
		Name:          strings.Title(name),
		NameLower:     strings.ToLower(name),
		NameUpper:     strings.ToUpper(name),
		NameCamel:     toCamelCase(name),
		Methods:       endpoint.Methods,
		GetPath:       endpoint.GetPath,
		PostPath:      endpoint.PostPath,
		PutPath:       endpoint.PutPath,
		DeletePath:    endpoint.DeletePath,
		PatchPath:     endpoint.PatchPath,
		Model:         endpoint.Model,
		ResponseModel: endpoint.ResponseModel,
		Description:   endpoint.Description,
		Summary:       endpoint.Summary,
		Tags:          endpoint.Tags,
		RequireAuth:   endpoint.RequireAuth,
		RateLimit:     endpoint.RateLimit,
		CacheEnabled:  endpoint.CacheEnabled,
		CacheTTL:      getOrDefault(endpoint.CacheTTL, 300),
		CustomHeaders: endpoint.CustomHeaders,
	}

	// Set method flags and extract path parameters
	for _, method := range endpoint.Methods {
		switch strings.ToUpper(method) {
		case "GET":
			data.HasGet = true
			data.PathParams = extractPathParams(endpoint.GetPath)
		case "POST":
			data.HasPost = true
		case "PUT":
			data.HasPut = true
			data.PathParams = extractPathParams(endpoint.PutPath)
		case "DELETE":
			data.HasDelete = true
			data.PathParams = extractPathParams(endpoint.DeletePath)
		case "PATCH":
			data.HasPatch = true
			data.PathParams = extractPathParams(endpoint.PatchPath)
		}
	}

	return data
}

func extractPathParams(path string) []string {
	if path == "" {
		return nil
	}

	var params []string
	parts := strings.Split(path, "/")
	for _, part := range parts {
		if strings.HasPrefix(part, ":") {
			params = append(params, strings.TrimPrefix(part, ":"))
		}
	}

	return params
}

func toCamelCase(str string) string {
	words := strings.FieldsFunc(str, func(c rune) bool {
		return c == '_' || c == '-' || c == ' '
	})

	if len(words) == 0 {
		return str
	}

	result := strings.ToLower(words[0])
	for _, word := range words[1:] {
		result += strings.Title(strings.ToLower(word))
	}

	return result
}

func getOrDefault(value, defaultValue int) int {
	if value == 0 {
		return defaultValue
	}
	return value
}
