package cmd

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"os"
	"strings"

	"github.com/gin-gonic/gin"
	"gopkg.in/yaml.v3"
)

// setupCryptoRoutes настраивает роуты для шифрования/дешифрования
func setupCryptoRoutes(api *gin.RouterGroup, passHash [32]byte) {
	// Encrypt: вход YAML (и опционально path). Если path указан: можно не передавать yaml,
	// тогда читаем из файла и пишем шифровку в этот же путь
	api.POST("/encrypt", func(c *gin.Context) {
		var req struct {
			Yaml string `json:"yaml"`
			Path string `json:"path"`
		}
		if err := c.BindJSON(&req); err != nil {
			c.JSON(400, gin.H{"error": fmt.Sprintf("bad json: %v", err)})
			return
		}
		if strings.TrimSpace(req.Yaml) == "" {
			c.JSON(400, gin.H{"error": "yaml is required"})
			return
		}

		// Извлекаем path из YAML для автоматической записи файла
		var yamlPath string
		if yamlStr := strings.TrimSpace(req.Yaml); yamlStr != "" {
			var config map[string]interface{}
			if err := yaml.Unmarshal([]byte(yamlStr), &config); err == nil {
				if pathVal, ok := config["path"].(string); ok {
					yamlPath = strings.TrimSpace(pathVal)
				}
			}
		}

		// Используем path из запроса или из YAML
		targetPath := strings.TrimSpace(req.Path)
		if targetPath == "" && yamlPath != "" {
			targetPath = yamlPath
		}

		encrypted, err := encryptBytes([]byte(req.Yaml), passHash[:])
		if err != nil {
			c.JSON(500, gin.H{"error": err.Error()})
			return
		}
		out := "ENCRYPTED:" + encrypted

		if targetPath != "" {
			if !isFileIOEnabled() {
				c.JSON(403, gin.H{"error": "file I/O is disabled; set GO_KNOCKER_ENABLE_FILE_IO=1 or remove GO_KNOCKER_ENABLE_FILE_IO=0"})
				return
			}
			if err := os.WriteFile(targetPath, []byte(out), 0600); err != nil {
				c.JSON(500, gin.H{"error": fmt.Sprintf("write failed: %v", err)})
				return
			}
			c.JSON(200, gin.H{"status": "ok", "encrypted": out, "path": targetPath})
			return
		}
		c.JSON(200, gin.H{"encrypted": out})
	})

	// Decrypt: вход ENCRYPTED:... (и опционально path). Если path указан: можно не передавать encrypted,
	// тогда читаем из файла и пишем расшифровку в этот же путь
	api.POST("/decrypt", func(c *gin.Context) {
		var req struct {
			Encrypted string `json:"encrypted"`
			Path      string `json:"path"`
		}
		if err := c.BindJSON(&req); err != nil {
			c.JSON(400, gin.H{"error": fmt.Sprintf("bad json: %v", err)})
			return
		}
		var enc string
		if strings.TrimSpace(req.Path) != "" && strings.TrimSpace(req.Encrypted) == "" {
			if !isFileIOEnabled() {
				c.JSON(403, gin.H{"error": "file I/O is disabled; set GO_KNOCKER_ENABLE_FILE_IO=1 or remove GO_KNOCKER_ENABLE_FILE_IO=0"})
				return
			}
			data, err := os.ReadFile(req.Path)
			if err != nil {
				c.JSON(400, gin.H{"error": fmt.Sprintf("read failed: %v", err)})
				return
			}
			enc = string(data)
		} else {
			enc = req.Encrypted
		}
		if !strings.HasPrefix(enc, "ENCRYPTED:") {
			c.JSON(400, gin.H{"error": "input must start with ENCRYPTED:"})
			return
		}
		plain, err := decryptString(enc[10:], passHash[:])
		if err != nil {
			c.JSON(400, gin.H{"error": err.Error()})
			return
		}

		// Извлекаем path из расшифрованного YAML
		var yamlPath string
		if plainStr := string(plain); strings.TrimSpace(plainStr) != "" {
			var config map[string]interface{}
			if err := yaml.Unmarshal(plain, &config); err == nil {
				if pathVal, ok := config["path"].(string); ok {
					yamlPath = strings.TrimSpace(pathVal)
				}
			}
		}

		// Используем path из запроса или из YAML
		targetPath := strings.TrimSpace(req.Path)
		if targetPath == "" && yamlPath != "" {
			targetPath = yamlPath
		}

		if targetPath != "" {
			if !isFileIOEnabled() {
				c.JSON(403, gin.H{"error": "file I/O is disabled; set GO_KNOCKER_ENABLE_FILE_IO=1 or remove GO_KNOCKER_ENABLE_FILE_IO=0"})
				return
			}
			if err := os.WriteFile(targetPath, plain, 0600); err != nil {
				c.JSON(500, gin.H{"error": fmt.Sprintf("write failed: %v", err)})
				return
			}
			c.JSON(200, gin.H{"status": "ok", "yaml": string(plain), "path": targetPath})
			return
		}
		c.JSON(200, gin.H{"yaml": string(plain)})
	})

	// Encrypt file on server filesystem
	api.POST("/encrypt-file", func(c *gin.Context) {
		if !isFileIOEnabled() {
			c.JSON(403, gin.H{"error": "file I/O is disabled; set GO_KNOCKER_ENABLE_FILE_IO=1 or remove GO_KNOCKER_ENABLE_FILE_IO=0"})
			return
		}
		var req struct {
			Path string `json:"path"`
		}
		if err := c.BindJSON(&req); err != nil || strings.TrimSpace(req.Path) == "" {
			c.JSON(400, gin.H{"error": "invalid path"})
			return
		}
		data, err := os.ReadFile(req.Path)
		if err != nil {
			c.JSON(400, gin.H{"error": fmt.Sprintf("read failed: %v", err)})
			return
		}
		encrypted, err := encryptBytes(data, passHash[:])
		if err != nil {
			c.JSON(500, gin.H{"error": err.Error()})
			return
		}
		out := "ENCRYPTED:" + encrypted
		if err := os.WriteFile(req.Path, []byte(out), 0600); err != nil {
			c.JSON(500, gin.H{"error": fmt.Sprintf("write failed: %v", err)})
			return
		}
		c.JSON(200, gin.H{"status": "ok", "encrypted": out})
	})

	// Decrypt file on server filesystem
	api.POST("/decrypt-file", func(c *gin.Context) {
		if !isFileIOEnabled() {
			c.JSON(403, gin.H{"error": "file I/O is disabled; set GO_KNOCKER_ENABLE_FILE_IO=1 or remove GO_KNOCKER_ENABLE_FILE_IO=0"})
			return
		}
		var req struct {
			Path string `json:"path"`
		}
		if err := c.BindJSON(&req); err != nil || strings.TrimSpace(req.Path) == "" {
			c.JSON(400, gin.H{"error": "invalid path"})
			return
		}
		data, err := os.ReadFile(req.Path)
		if err != nil {
			c.JSON(400, gin.H{"error": fmt.Sprintf("read failed: %v", err)})
			return
		}
		enc := string(data)
		if !strings.HasPrefix(enc, "ENCRYPTED:") {
			c.JSON(400, gin.H{"error": "file must start with ENCRYPTED:"})
			return
		}
		plain, err := decryptString(enc[10:], passHash[:])
		if err != nil {
			c.JSON(400, gin.H{"error": err.Error()})
			return
		}
		if err := os.WriteFile(req.Path, plain, 0600); err != nil {
			c.JSON(500, gin.H{"error": fmt.Sprintf("write failed: %v", err)})
			return
		}
		c.JSON(200, gin.H{"status": "ok", "yaml": string(plain)})
	})
}

// encryptBytes шифрует байты с помощью AES-GCM
func encryptBytes(data []byte, key []byte) (string, error) {
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	nonce := make([]byte, gcm.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return "", err
	}

	ciphertext := gcm.Seal(nonce, nonce, data, nil)
	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// decryptString дешифрует строку с помощью AES-GCM
func decryptString(encrypted string, key []byte) ([]byte, error) {
	data, err := base64.StdEncoding.DecodeString(encrypted)
	if err != nil {
		return nil, err
	}

	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, err
	}

	nonceSize := gcm.NonceSize()
	if len(data) < nonceSize {
		return nil, fmt.Errorf("ciphertext too short")
	}

	nonce, ciphertext := data[:nonceSize], data[nonceSize:]
	plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, err
	}

	return plaintext, nil
}
