package caddy

import (
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/Wikid82/charon/backend/internal/models"
)

// Executor defines an interface for executing shell commands.
type Executor interface {
	Execute(name string, args ...string) ([]byte, error)
}

// DefaultExecutor implements Executor using os/exec.
type DefaultExecutor struct{}

func (e *DefaultExecutor) Execute(name string, args ...string) ([]byte, error) {
	return exec.Command(name, args...).Output()
}

// CaddyConfig represents the root structure of Caddy's JSON config.
type CaddyConfig struct {
	Apps *CaddyApps `json:"apps,omitempty"`
}

// CaddyApps contains application-specific configurations.
type CaddyApps struct {
	HTTP *CaddyHTTP `json:"http,omitempty"`
}

// CaddyHTTP represents the HTTP app configuration.
type CaddyHTTP struct {
	Servers map[string]*CaddyServer `json:"servers,omitempty"`
}

// CaddyServer represents a single server configuration.
type CaddyServer struct {
	Listen                []string      `json:"listen,omitempty"`
	Routes                []*CaddyRoute `json:"routes,omitempty"`
	TLSConnectionPolicies interface{}   `json:"tls_connection_policies,omitempty"`
}

// CaddyRoute represents a single route with matchers and handlers.
type CaddyRoute struct {
	Match  []*CaddyMatcher `json:"match,omitempty"`
	Handle []*CaddyHandler `json:"handle,omitempty"`
}

// CaddyMatcher represents route matching criteria.
type CaddyMatcher struct {
	Host []string `json:"host,omitempty"`
}

// CaddyHandler represents a handler in the route.
type CaddyHandler struct {
	Handler   string      `json:"handler"`
	Upstreams interface{} `json:"upstreams,omitempty"`
	Headers   interface{} `json:"headers,omitempty"`
	Routes    interface{} `json:"routes,omitempty"` // For subroute handlers
}

// ParsedHost represents a single host detected during Caddyfile import.
type ParsedHost struct {
	DomainNames      string   `json:"domain_names"`
	ForwardScheme    string   `json:"forward_scheme"`
	ForwardHost      string   `json:"forward_host"`
	ForwardPort      int      `json:"forward_port"`
	SSLForced        bool     `json:"ssl_forced"`
	WebsocketSupport bool     `json:"websocket_support"`
	RawJSON          string   `json:"raw_json"` // Original Caddy JSON for this route
	Warnings         []string `json:"warnings"` // Unsupported features
}

// ImportResult contains parsed hosts and detected conflicts.
type ImportResult struct {
	Hosts     []ParsedHost `json:"hosts"`
	Conflicts []string     `json:"conflicts"`
	Errors    []string     `json:"errors"`
}

// Importer handles Caddyfile parsing and conversion to Charon models.
type Importer struct {
	caddyBinaryPath string
	executor        Executor
}

// NewImporter creates a new Caddyfile importer.
func NewImporter(binaryPath string) *Importer {
	if binaryPath == "" {
		binaryPath = "caddy" // Default to PATH
	}
	return &Importer{
		caddyBinaryPath: binaryPath,
		executor:        &DefaultExecutor{},
	}
}

// forceSplitFallback used in tests to exercise the fallback branch
var forceSplitFallback bool

// ParseCaddyfile reads a Caddyfile and converts it to Caddy JSON.
func (i *Importer) ParseCaddyfile(caddyfilePath string) ([]byte, error) {
	// Sanitize the incoming path to detect forbidden traversal sequences.
	clean := filepath.Clean(caddyfilePath)
	if clean == "" || clean == "." {
		return nil, fmt.Errorf("invalid caddyfile path")
	}
	if strings.Contains(clean, ".."+string(os.PathSeparator)) || strings.HasPrefix(clean, "..") {
		return nil, fmt.Errorf("invalid caddyfile path")
	}
	if _, err := os.Stat(clean); os.IsNotExist(err) {
		return nil, fmt.Errorf("caddyfile not found: %s", clean)
	}

	output, err := i.executor.Execute(i.caddyBinaryPath, "adapt", "--config", clean, "--adapter", "caddyfile")
	if err != nil {
		return nil, fmt.Errorf("caddy adapt failed: %w (output: %s)", err, string(output))
	}

	return output, nil
}

// extractHandlers recursively extracts handlers from a list, flattening subroutes.
func (i *Importer) extractHandlers(handles []*CaddyHandler) []*CaddyHandler {
	var result []*CaddyHandler

	for _, handler := range handles {
		// Regular handler, add it directly
		if handler.Handler != "subroute" {
			result = append(result, handler)
			continue
		}

		// It's a subroute; extract handlers from its first route
		routes, ok := handler.Routes.([]interface{})
		if !ok || len(routes) == 0 {
			continue
		}
		subroute, ok := routes[0].(map[string]interface{})
		if !ok {
			continue
		}
		subhandles, ok := subroute["handle"].([]interface{})
		if !ok {
			continue
		}
		// Convert the subhandles to CaddyHandler objects
		for _, sh := range subhandles {
			shMap, ok := sh.(map[string]interface{})
			if !ok {
				continue
			}
			subHandler := &CaddyHandler{}
			if handlerType, ok := shMap["handler"].(string); ok {
				subHandler.Handler = handlerType
			}
			if upstreams, ok := shMap["upstreams"]; ok {
				subHandler.Upstreams = upstreams
			}
			if headers, ok := shMap["headers"]; ok {
				subHandler.Headers = headers
			}
			result = append(result, subHandler)
		}
	}

	return result
}

// ExtractHosts parses Caddy JSON and extracts proxy host information.
func (i *Importer) ExtractHosts(caddyJSON []byte) (*ImportResult, error) {
	var config CaddyConfig
	if err := json.Unmarshal(caddyJSON, &config); err != nil {
		return nil, fmt.Errorf("parsing caddy json: %w", err)
	}

	result := &ImportResult{
		Hosts:     []ParsedHost{},
		Conflicts: []string{},
		Errors:    []string{},
	}

	if config.Apps == nil || config.Apps.HTTP == nil || config.Apps.HTTP.Servers == nil {
		return result, nil // Empty config
	}

	seenDomains := make(map[string]bool)

	for serverName, server := range config.Apps.HTTP.Servers {
		// Detect if this server uses SSL based on listen address or TLS policies
		serverUsesSSL := server.TLSConnectionPolicies != nil
		for _, listenAddr := range server.Listen {
			// Check if listening on :443 or any HTTPS port indicator
			if strings.Contains(listenAddr, ":443") || strings.HasSuffix(listenAddr, "443") {
				serverUsesSSL = true
				break
			}
		}

		for routeIdx, route := range server.Routes {
			for _, match := range route.Match {
				for _, hostMatcher := range match.Host {
					domain := hostMatcher

					// Check for duplicate domains (report domain names only)
					if seenDomains[domain] {
						result.Conflicts = append(result.Conflicts, domain)
						continue
					}
					seenDomains[domain] = true

					// Extract reverse proxy handler
					host := ParsedHost{
						DomainNames: domain,
						SSLForced:   strings.HasPrefix(domain, "https") || serverUsesSSL,
					}

					// Find reverse_proxy handler (may be nested in subroute)
					handlers := i.extractHandlers(route.Handle)

					for _, handler := range handlers {
						if handler.Handler == "reverse_proxy" {
							upstreams, _ := handler.Upstreams.([]interface{})
							if len(upstreams) > 0 {
								if upstream, ok := upstreams[0].(map[string]interface{}); ok {
									dial, _ := upstream["dial"].(string)
									if dial != "" {
										hostStr, portStr, err := net.SplitHostPort(dial)
										if err == nil && !forceSplitFallback {
											host.ForwardHost = hostStr
											if _, err := fmt.Sscanf(portStr, "%d", &host.ForwardPort); err != nil {
												host.ForwardPort = 80
											}
										} else {
											// Fallback: assume dial is just the host or has some other format
											// Try to handle simple "host:port" manually if net.SplitHostPort failed for some reason
											// or assume it's just a host
											parts := strings.Split(dial, ":")
											if len(parts) == 2 {
												host.ForwardHost = parts[0]
												if _, err := fmt.Sscanf(parts[1], "%d", &host.ForwardPort); err != nil {
													host.ForwardPort = 80
												}
											} else {
												host.ForwardHost = dial
												host.ForwardPort = 80
											}
										}
									}
								}
							}

							// Check for websocket support
							if headers, ok := handler.Headers.(map[string]interface{}); ok {
								if upgrade, ok := headers["Upgrade"].([]interface{}); ok {
									for _, v := range upgrade {
										if v == "websocket" {
											host.WebsocketSupport = true
											break
										}
									}
								}
							}

							// Default scheme
							host.ForwardScheme = "http"
							if host.SSLForced {
								host.ForwardScheme = "https"
							}
						}

						// Detect unsupported features
						if handler.Handler == "rewrite" {
							host.Warnings = append(host.Warnings, "Rewrite rules not supported - manual configuration required")
						}
						if handler.Handler == "file_server" {
							host.Warnings = append(host.Warnings, "File server directives not supported")
						}
					}

					// Store raw JSON for this route
					routeJSON, _ := json.Marshal(map[string]interface{}{
						"server": serverName,
						"route":  routeIdx,
						"data":   route,
					})
					host.RawJSON = string(routeJSON)

					result.Hosts = append(result.Hosts, host)
				}
			}
		}
	}

	return result, nil
}

// ImportFile performs complete import: parse Caddyfile and extract hosts.
func (i *Importer) ImportFile(caddyfilePath string) (*ImportResult, error) {
	caddyJSON, err := i.ParseCaddyfile(caddyfilePath)
	if err != nil {
		return nil, err
	}

	return i.ExtractHosts(caddyJSON)
}

// ConvertToProxyHosts converts parsed hosts to ProxyHost models.
func ConvertToProxyHosts(parsedHosts []ParsedHost) []models.ProxyHost {
	hosts := make([]models.ProxyHost, 0, len(parsedHosts))

	for _, parsed := range parsedHosts {
		if parsed.ForwardHost == "" || parsed.ForwardPort == 0 {
			continue // Skip invalid entries
		}

		hosts = append(hosts, models.ProxyHost{
			Name:             parsed.DomainNames, // Can be customized by user during review
			DomainNames:      parsed.DomainNames,
			ForwardScheme:    parsed.ForwardScheme,
			ForwardHost:      parsed.ForwardHost,
			ForwardPort:      parsed.ForwardPort,
			SSLForced:        parsed.SSLForced,
			WebsocketSupport: parsed.WebsocketSupport,
		})
	}

	return hosts
}

// ValidateCaddyBinary checks if the Caddy binary is available.
func (i *Importer) ValidateCaddyBinary() error {
	_, err := i.executor.Execute(i.caddyBinaryPath, "version")
	if err != nil {
		return errors.New("caddy binary not found or not executable")
	}
	return nil
}

// BackupCaddyfile creates a timestamped backup of the original Caddyfile.
func BackupCaddyfile(originalPath, backupDir string) (string, error) {
	if err := os.MkdirAll(backupDir, 0o755); err != nil {
		return "", fmt.Errorf("creating backup directory: %w", err)
	}

	timestamp := fmt.Sprintf("%d", os.Getpid()) // Simple timestamp placeholder
	// Ensure the backup path is contained within backupDir to prevent path traversal
	backupFile := fmt.Sprintf("Caddyfile.%s.backup", timestamp)
	// Create a safe join with backupDir
	backupPath := filepath.Join(backupDir, backupFile)

	// Validate the original path: avoid traversal elements pointing outside backupDir
	clean := filepath.Clean(originalPath)
	if clean == "" || clean == "." {
		return "", fmt.Errorf("invalid original path")
	}
	if strings.Contains(clean, ".."+string(os.PathSeparator)) || strings.HasPrefix(clean, "..") {
		return "", fmt.Errorf("invalid original path")
	}
	input, err := os.ReadFile(clean)
	if err != nil {
		return "", fmt.Errorf("reading original file: %w", err)
	}

	if err := os.WriteFile(backupPath, input, 0o644); err != nil {
		return "", fmt.Errorf("writing backup: %w", err)
	}

	return backupPath, nil
}
