package handlers

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"gorm.io/gorm"

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

// jsonImportSession stores the parsed content for a JSON import session.
type jsonImportSession struct {
	SourceType   string // "charon" or "npm"
	CharonExport *CharonExport
	NPMExport    *NPMExport
}

// jsonImportSessions stores parsed exports keyed by session UUID.
// TODO: Implement session expiration to prevent memory leaks (e.g., TTL-based cleanup).
var (
	jsonImportSessions   = make(map[string]jsonImportSession)
	jsonImportSessionsMu sync.RWMutex
)

// CharonExport represents the top-level structure of a Charon export file.
type CharonExport struct {
	Version     string             `json:"version"`
	ExportedAt  time.Time          `json:"exported_at"`
	ProxyHosts  []CharonProxyHost  `json:"proxy_hosts"`
	AccessLists []CharonAccessList `json:"access_lists"`
	DNSRecords  []CharonDNSRecord  `json:"dns_records"`
}

// CharonProxyHost represents a proxy host in Charon export format.
type CharonProxyHost struct {
	UUID             string `json:"uuid"`
	Name             string `json:"name"`
	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"`
	HTTP2Support     bool   `json:"http2_support"`
	HSTSEnabled      bool   `json:"hsts_enabled"`
	HSTSSubdomains   bool   `json:"hsts_subdomains"`
	BlockExploits    bool   `json:"block_exploits"`
	WebsocketSupport bool   `json:"websocket_support"`
	Application      string `json:"application"`
	Enabled          bool   `json:"enabled"`
	AdvancedConfig   string `json:"advanced_config"`
	WAFDisabled      bool   `json:"waf_disabled"`
	UseDNSChallenge  bool   `json:"use_dns_challenge"`
}

// CharonAccessList represents an access list in Charon export format.
type CharonAccessList struct {
	UUID             string `json:"uuid"`
	Name             string `json:"name"`
	Description      string `json:"description"`
	Type             string `json:"type"`
	IPRules          string `json:"ip_rules"`
	CountryCodes     string `json:"country_codes"`
	LocalNetworkOnly bool   `json:"local_network_only"`
	Enabled          bool   `json:"enabled"`
}

// CharonDNSRecord represents a DNS record in Charon export format.
type CharonDNSRecord struct {
	UUID       string `json:"uuid"`
	Name       string `json:"name"`
	Type       string `json:"type"`
	Value      string `json:"value"`
	TTL        int    `json:"ttl"`
	ProviderID uint   `json:"provider_id"`
}

// JSONImportHandler handles JSON configuration imports (both Charon and NPM formats).
type JSONImportHandler struct {
	db           *gorm.DB
	proxyHostSvc *services.ProxyHostService
}

// NewJSONImportHandler creates a new JSON import handler.
func NewJSONImportHandler(db *gorm.DB) *JSONImportHandler {
	return &JSONImportHandler{
		db:           db,
		proxyHostSvc: services.NewProxyHostService(db),
	}
}

// RegisterRoutes registers JSON import routes.
func (h *JSONImportHandler) RegisterRoutes(router *gin.RouterGroup) {
	router.POST("/import/json/upload", h.Upload)
	router.POST("/import/json/commit", h.Commit)
	router.POST("/import/json/cancel", h.Cancel)
}

// Upload parses a JSON export (Charon or NPM format) and returns a preview.
func (h *JSONImportHandler) Upload(c *gin.Context) {
	var req struct {
		Content string `json:"content" binding:"required"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Try Charon format first
	var charonExport CharonExport
	if err := json.Unmarshal([]byte(req.Content), &charonExport); err == nil && h.isCharonFormat(charonExport) {
		h.handleCharonUpload(c, charonExport)
		return
	}

	// Fall back to NPM format
	var npmExport NPMExport
	if err := json.Unmarshal([]byte(req.Content), &npmExport); err == nil && len(npmExport.ProxyHosts) > 0 {
		h.handleNPMUpload(c, npmExport)
		return
	}

	c.JSON(http.StatusBadRequest, gin.H{"error": "unrecognized JSON format - must be Charon or NPM export"})
}

// isCharonFormat checks if the export is in Charon format.
func (h *JSONImportHandler) isCharonFormat(export CharonExport) bool {
	return export.Version != "" || len(export.ProxyHosts) > 0
}

// handleCharonUpload processes a Charon format export.
func (h *JSONImportHandler) handleCharonUpload(c *gin.Context, export CharonExport) {
	result := h.convertCharonToImportResult(export)

	if len(result.Hosts) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "no proxy hosts found in Charon export"})
		return
	}

	existingHosts, _ := h.proxyHostSvc.List()
	existingDomainsMap := make(map[string]models.ProxyHost)
	for _, eh := range existingHosts {
		existingDomainsMap[eh.DomainNames] = eh
	}

	conflictDetails := make(map[string]gin.H)
	for _, ph := range result.Hosts {
		if existing, found := existingDomainsMap[ph.DomainNames]; found {
			result.Conflicts = append(result.Conflicts, ph.DomainNames)
			conflictDetails[ph.DomainNames] = gin.H{
				"existing": gin.H{
					"forward_scheme": existing.ForwardScheme,
					"forward_host":   existing.ForwardHost,
					"forward_port":   existing.ForwardPort,
					"ssl_forced":     existing.SSLForced,
					"websocket":      existing.WebsocketSupport,
					"enabled":        existing.Enabled,
				},
				"imported": gin.H{
					"forward_scheme": ph.ForwardScheme,
					"forward_host":   ph.ForwardHost,
					"forward_port":   ph.ForwardPort,
					"ssl_forced":     ph.SSLForced,
					"websocket":      ph.WebsocketSupport,
				},
			}
		}
	}

	sid := uuid.NewString()

	// Store the parsed export in session storage for later commit
	jsonImportSessionsMu.Lock()
	jsonImportSessions[sid] = jsonImportSession{
		SourceType:   "charon",
		CharonExport: &export,
	}
	jsonImportSessionsMu.Unlock()

	c.JSON(http.StatusOK, gin.H{
		"session":          gin.H{"id": sid, "state": "transient", "source_type": "charon"},
		"preview":          result,
		"conflict_details": conflictDetails,
		"charon_export": gin.H{
			"version":      export.Version,
			"exported_at":  export.ExportedAt,
			"proxy_hosts":  len(export.ProxyHosts),
			"access_lists": len(export.AccessLists),
			"dns_records":  len(export.DNSRecords),
		},
	})
}

// handleNPMUpload processes an NPM format export.
func (h *JSONImportHandler) handleNPMUpload(c *gin.Context, export NPMExport) {
	npmHandler := NewNPMImportHandler(h.db)
	result := npmHandler.convertNPMToImportResult(export)

	if len(result.Hosts) == 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "no proxy hosts found in NPM export"})
		return
	}

	existingHosts, _ := h.proxyHostSvc.List()
	existingDomainsMap := make(map[string]models.ProxyHost)
	for _, eh := range existingHosts {
		existingDomainsMap[eh.DomainNames] = eh
	}

	conflictDetails := make(map[string]gin.H)
	for _, ph := range result.Hosts {
		if existing, found := existingDomainsMap[ph.DomainNames]; found {
			result.Conflicts = append(result.Conflicts, ph.DomainNames)
			conflictDetails[ph.DomainNames] = gin.H{
				"existing": gin.H{
					"forward_scheme": existing.ForwardScheme,
					"forward_host":   existing.ForwardHost,
					"forward_port":   existing.ForwardPort,
					"ssl_forced":     existing.SSLForced,
					"websocket":      existing.WebsocketSupport,
					"enabled":        existing.Enabled,
				},
				"imported": gin.H{
					"forward_scheme": ph.ForwardScheme,
					"forward_host":   ph.ForwardHost,
					"forward_port":   ph.ForwardPort,
					"ssl_forced":     ph.SSLForced,
					"websocket":      ph.WebsocketSupport,
				},
			}
		}
	}

	sid := uuid.NewString()

	// Store the parsed export in session storage for later commit
	jsonImportSessionsMu.Lock()
	jsonImportSessions[sid] = jsonImportSession{
		SourceType: "npm",
		NPMExport:  &export,
	}
	jsonImportSessionsMu.Unlock()

	c.JSON(http.StatusOK, gin.H{
		"session":          gin.H{"id": sid, "state": "transient", "source_type": "npm"},
		"preview":          result,
		"conflict_details": conflictDetails,
		"npm_export": gin.H{
			"proxy_hosts":  len(export.ProxyHosts),
			"access_lists": len(export.AccessLists),
			"certificates": len(export.Certificates),
		},
	})
}

// Commit finalizes the JSON import with user's conflict resolutions.
func (h *JSONImportHandler) Commit(c *gin.Context) {
	var req struct {
		SessionUUID string            `json:"session_uuid" binding:"required"`
		Resolutions map[string]string `json:"resolutions"`
		Names       map[string]string `json:"names"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Retrieve the stored session
	jsonImportSessionsMu.RLock()
	session, ok := jsonImportSessions[req.SessionUUID]
	jsonImportSessionsMu.RUnlock()

	if !ok {
		c.JSON(http.StatusNotFound, gin.H{"error": "session not found or expired"})
		return
	}

	// Route to the appropriate commit handler based on source type
	if session.SourceType == "charon" && session.CharonExport != nil {
		h.commitCharonImport(c, *session.CharonExport, req.Resolutions, req.Names, req.SessionUUID)
		return
	}

	if session.SourceType == "npm" && session.NPMExport != nil {
		h.commitNPMImport(c, *session.NPMExport, req.Resolutions, req.Names, req.SessionUUID)
		return
	}

	c.JSON(http.StatusBadRequest, gin.H{"error": "invalid session state"})
}

// Cancel cancels a JSON import session and cleans up resources.
func (h *JSONImportHandler) Cancel(c *gin.Context) {
	var req struct {
		SessionUUID string `json:"session_uuid"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Clean up session if it exists
	jsonImportSessionsMu.Lock()
	delete(jsonImportSessions, req.SessionUUID)
	jsonImportSessionsMu.Unlock()

	c.JSON(http.StatusOK, gin.H{"status": "cancelled"})
}

// commitCharonImport commits a Charon format import.
func (h *JSONImportHandler) commitCharonImport(c *gin.Context, export CharonExport, resolutions, names map[string]string, sessionUUID string) {
	result := h.convertCharonToImportResult(export)
	proxyHosts := caddy.ConvertToProxyHosts(result.Hosts)

	created := 0
	updated := 0
	skipped := 0
	errors := []string{}

	existingHosts, _ := h.proxyHostSvc.List()
	existingMap := make(map[string]*models.ProxyHost)
	for i := range existingHosts {
		existingMap[existingHosts[i].DomainNames] = &existingHosts[i]
	}

	for _, host := range proxyHosts {
		action := resolutions[host.DomainNames]

		if customName, ok := names[host.DomainNames]; ok && customName != "" {
			host.Name = customName
		}

		if action == "skip" || action == "keep" {
			skipped++
			continue
		}

		if action == "rename" {
			host.DomainNames += "-imported"
		}

		if action == "overwrite" {
			if existing, found := existingMap[host.DomainNames]; found {
				host.ID = existing.ID
				host.UUID = existing.UUID
				host.CertificateID = existing.CertificateID
				host.CreatedAt = existing.CreatedAt

				if err := h.proxyHostSvc.Update(&host); err != nil {
					errors = append(errors, fmt.Sprintf("%s: %s", host.DomainNames, err.Error()))
				} else {
					updated++
				}
				continue
			}
		}

		host.UUID = uuid.NewString()
		if err := h.proxyHostSvc.Create(&host); err != nil {
			errors = append(errors, fmt.Sprintf("%s: %s", host.DomainNames, err.Error()))
		} else {
			created++
		}
	}

	// Clean up session after successful commit
	jsonImportSessionsMu.Lock()
	delete(jsonImportSessions, sessionUUID)
	jsonImportSessionsMu.Unlock()

	c.JSON(http.StatusOK, gin.H{
		"created": created,
		"updated": updated,
		"skipped": skipped,
		"errors":  errors,
	})
}

// commitNPMImport commits an NPM format import.
func (h *JSONImportHandler) commitNPMImport(c *gin.Context, export NPMExport, resolutions, names map[string]string, sessionUUID string) {
	npmHandler := NewNPMImportHandler(h.db)
	result := npmHandler.convertNPMToImportResult(export)
	proxyHosts := caddy.ConvertToProxyHosts(result.Hosts)

	created := 0
	updated := 0
	skipped := 0
	errors := []string{}

	existingHosts, _ := h.proxyHostSvc.List()
	existingMap := make(map[string]*models.ProxyHost)
	for i := range existingHosts {
		existingMap[existingHosts[i].DomainNames] = &existingHosts[i]
	}

	for _, host := range proxyHosts {
		action := resolutions[host.DomainNames]

		if customName, ok := names[host.DomainNames]; ok && customName != "" {
			host.Name = customName
		}

		if action == "skip" || action == "keep" {
			skipped++
			continue
		}

		if action == "rename" {
			host.DomainNames += "-imported"
		}

		if action == "overwrite" {
			if existing, found := existingMap[host.DomainNames]; found {
				host.ID = existing.ID
				host.UUID = existing.UUID
				host.CertificateID = existing.CertificateID
				host.CreatedAt = existing.CreatedAt

				if err := h.proxyHostSvc.Update(&host); err != nil {
					errors = append(errors, fmt.Sprintf("%s: %s", host.DomainNames, err.Error()))
				} else {
					updated++
				}
				continue
			}
		}

		host.UUID = uuid.NewString()
		if err := h.proxyHostSvc.Create(&host); err != nil {
			errors = append(errors, fmt.Sprintf("%s: %s", host.DomainNames, err.Error()))
		} else {
			created++
		}
	}

	// Clean up session after successful commit
	jsonImportSessionsMu.Lock()
	delete(jsonImportSessions, sessionUUID)
	jsonImportSessionsMu.Unlock()

	c.JSON(http.StatusOK, gin.H{
		"created": created,
		"updated": updated,
		"skipped": skipped,
		"errors":  errors,
	})
}

// convertCharonToImportResult converts Charon export format to ImportResult.
func (h *JSONImportHandler) convertCharonToImportResult(export CharonExport) *caddy.ImportResult {
	result := &caddy.ImportResult{
		Hosts:     []caddy.ParsedHost{},
		Conflicts: []string{},
		Errors:    []string{},
	}

	for _, ch := range export.ProxyHosts {
		if ch.DomainNames == "" {
			result.Errors = append(result.Errors, fmt.Sprintf("host %s has no domain names", ch.UUID))
			continue
		}

		scheme := ch.ForwardScheme
		if scheme == "" {
			scheme = "http"
		}

		port := ch.ForwardPort
		if port == 0 {
			port = 80
		}

		warnings := []string{}
		if ch.AdvancedConfig != "" && !isValidJSON(ch.AdvancedConfig) {
			warnings = append(warnings, "Advanced config may need review")
		}

		host := caddy.ParsedHost{
			DomainNames:      ch.DomainNames,
			ForwardScheme:    scheme,
			ForwardHost:      ch.ForwardHost,
			ForwardPort:      port,
			SSLForced:        ch.SSLForced,
			WebsocketSupport: ch.WebsocketSupport,
			Warnings:         warnings,
		}

		rawJSON, _ := json.Marshal(ch)
		host.RawJSON = string(rawJSON)

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

	return result
}

// isValidJSON checks if a string is valid JSON.
func isValidJSON(s string) bool {
	s = strings.TrimSpace(s)
	if s == "" {
		return true
	}
	var js json.RawMessage
	return json.Unmarshal([]byte(s), &js) == nil
}
