package main

import (
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"io"
	"log"
	"os"
	"regexp"
	"strconv"
	"strings"
)

// writeFileIfChanged записывает файл только если содержимое изменилось
func writeFileIfChanged(filepath string, content string) (bool, error) {
	// Проверяем существует ли файл
	oldHash := ""
	if _, err := os.Stat(filepath); err == nil {
		var hashErr error
		oldHash, hashErr = getFileHash(filepath)
		if hashErr != nil {
			log.Printf("    ⚠ Ошибка чтения старого файла, перезаписываем")
			oldHash = ""
		}
	}

	newHash := getStringHash(content)

	if oldHash != "" && oldHash == newHash {
		return false, nil
	}

	if oldHash != "" {
		log.Printf("    → Хеш изменился: old=%s... new=%s...", oldHash[:8], newHash[:8])
	}

	err := os.WriteFile(filepath, []byte(content), 0644)
	if err != nil {
		return false, err
	}

	return true, nil
}

// getFileHash вычисляет SHA256 хеш файла
func getFileHash(filepath string) (string, error) {
	file, err := os.Open(filepath)
	if err != nil {
		return "", err
	}
	defer file.Close()

	hash := sha256.New()
	if _, err := io.Copy(hash, file); err != nil {
		return "", err
	}

	return hex.EncodeToString(hash.Sum(nil)), nil
}

// getStringHash вычисляет SHA256 хеш строки
func getStringHash(content string) string {
	hash := sha256.Sum256([]byte(content))
	return hex.EncodeToString(hash[:])
}

// extractPodIPFromConfig извлекает POD_IP из существующего nginx.conf
func extractPodIPFromConfig(filepath string) string {
	data, err := os.ReadFile(filepath)
	if err != nil {
		return ""
	}

	lines := strings.Split(string(data), "\n")
	for _, line := range lines {
		if strings.Contains(line, "env POD_IP=") {
			parts := strings.Split(line, "=")
			if len(parts) >= 2 {
				ip := strings.TrimSpace(strings.TrimSuffix(parts[1], ";"))
				return ip
			}
		}
	}

	return ""
}

// processDomainsWithWWW обрабатывает список доменов и добавляет www. варианты если нужно
func processDomainsWithWWW(domains []string) []string {
	domainMap := make(map[string]bool)
	for _, domain := range domains {
		domainMap[domain] = true
	}

	result := make([]string, 0, len(domains)*2)

	for _, domain := range domains {
		result = append(result, domain)

		if strings.HasPrefix(domain, "*.") || strings.HasPrefix(domain, "www.") {
			continue
		}

		wwwDomain := "www." + domain
		if !domainMap[wwwDomain] {
			result = append(result, wwwDomain)
			domainMap[wwwDomain] = true
		}
	}

	return result
}

// sanitizeDomainForNginx санитизирует домен для nginx конфигов
// Заменяет _ на - чтобы соответствовать санитизации в Angie
func sanitizeDomainForNginx(domain string) string {
	return strings.ReplaceAll(domain, "_", "-")
}

// sanitizeDomainsForNginx санитизирует массив доменов для nginx конфигов
func sanitizeDomainsForNginx(domains []string) []string {
	result := make([]string, len(domains))
	for i, domain := range domains {
		result[i] = sanitizeDomainForNginx(domain)
	}
	return result
}

// sanitizeServerName убирает недопустимые символы из server_name для использования в имени файла
func sanitizeServerName(serverName string) string {
	sanitized := strings.ReplaceAll(serverName, ".", "_")
	sanitized = strings.ReplaceAll(sanitized, "*", "wildcard")
	return sanitized
}

// replaceMTLSPaths заменяет пути к mTLS сертификатам на путь внутри контейнера
func replaceMTLSPaths(content string) string {
	content = strings.ReplaceAll(content, "/etc/ssl/certs/", "/opt/ptaf/conf/main/")
	content = strings.ReplaceAll(content, "/etc/ssl/private/", "/opt/ptaf/conf/main/")
	return content
}

// getMapKeys возвращает ключи из map в виде слайса (для логирования)
func getMapKeys(m map[string]bool) []string {
	keys := make([]string, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	return keys
}

// hasMTLSCertificates проверяет содержит ли строка директивы mTLS сертификатов
func hasMTLSCertificates(content string) bool {
	return strings.Contains(content, "proxy_ssl_certificate")
}

// getCustomServerDirectives извлекает кастомные server директивы из sql.NullString
func getCustomServerDirectives(nullStr sql.NullString) string {
	if nullStr.Valid && strings.TrimSpace(nullStr.String) != "" {
		return strings.TrimSpace(nullStr.String)
	}
	return ""
}

// parseCustomServerBlocks парсит директивы для поиска [SERVER_BLOCK] директив.
// sanitize = true для Nginx (санитизирует домены _ → -), false для Angie (оставляет оригинальные домены)
// Синтаксис:
//
//	[SERVER_BLOCK:PORT:SERVERNAME]...[/SERVER_BLOCK]
//	[SERVER_BLOCK:PORT:SERVERNAME:ALIAS1:ALIAS2:...]...[/SERVER_BLOCK]
func parseCustomServerBlocks(customDirectives string, sanitize bool) []CustomServerBlock {
	blocks := []CustomServerBlock{}

	if customDirectives == "" {
		return blocks
	}

	re := regexp.MustCompile(`(?s)\[SERVER_BLOCK:(\d+):([^\]]+)\]\s*\n(.*?)\[/SERVER_BLOCK\]`)
	matches := re.FindAllStringSubmatch(customDirectives, -1)

	for _, match := range matches {
		port, _ := strconv.Atoi(match[1])
		namesRaw := strings.TrimSpace(match[2])
		content := strings.TrimSpace(match[3])

		names := strings.Split(namesRaw, ":")

		var serverName string
		if sanitize {
			serverName = sanitizeDomainForNginx(strings.TrimSpace(names[0]))
		} else {
			serverName = strings.TrimSpace(names[0])
		}

		var aliases []string
		if len(names) > 1 {
			for _, alias := range names[1:] {
				trimmed := strings.TrimSpace(alias)
				if trimmed != "" {
					if sanitize {
						aliases = append(aliases, sanitizeDomainForNginx(trimmed))
					} else {
						aliases = append(aliases, trimmed)
					}
				}
			}
		}

		isHTTPS := port == 443 || port >= 8443

		content = replaceMTLSPaths(content)

		blocks = append(blocks, CustomServerBlock{
			Port:       port,
			ServerName: serverName,
			Aliases:    aliases,
			Content:    content,
			IsHTTPS:    isHTTPS,
		})
	}

	return blocks
}

// stripServerBlocks удаляет SERVER_BLOCK директивы из строки
func stripServerBlocks(s string) string {
	re := regexp.MustCompile(`(?s)\[SERVER_BLOCK:\d+:[^\]]+\].*?\[/SERVER_BLOCK\]\s*`)
	return strings.TrimSpace(re.ReplaceAllString(s, ""))
}

// collectServerBlockDomains собирает домены из SERVER_BLOCK по портам и возвращает
// (byPort map[port]map[domain]bool, allDomains map[domain]bool)
func collectServerBlockDomains(blocks []CustomServerBlock) (map[int]map[string]bool, map[string]bool) {
	byPort := make(map[int]map[string]bool)
	all := make(map[string]bool)

	for _, block := range blocks {
		if byPort[block.Port] == nil {
			byPort[block.Port] = make(map[string]bool)
		}
		byPort[block.Port][block.ServerName] = true
		all[block.ServerName] = true
		for _, alias := range block.Aliases {
			byPort[block.Port][alias] = true
			all[alias] = true
		}
	}

	return byPort, all
}

// filterDomainsForPort фильтрует домены для конкретного порта, исключая те, что в SERVER_BLOCK
func filterDomainsForPort(port int, allDomains []string, serverBlockDomainsByPort map[int]map[string]bool, allServerBlockDomains map[string]bool) []string {
	var result []string

	for _, domain := range allDomains {
		if serverBlockDomainsByPort[port][domain] {
			continue
		}

		if port == 80 || port == 443 {
			result = append(result, domain)
		} else {
			// Кастомный порт: исключаем ВСЕ домены из SERVER_BLOCK
			if !allServerBlockDomains[domain] {
				result = append(result, domain)
			}
		}
	}

	return result
}

// writeIndentedLines записывает строки с отступами для nginx location блоков
func writeIndentedLines(w stringWriter, content string) {
	lines := strings.Split(content, "\n")
	for _, line := range lines {
		if strings.TrimSpace(line) != "" {
			if strings.HasPrefix(line, "        ") {
				w.WriteString(line + "\n")
			} else if strings.HasPrefix(line, "    ") {
				w.WriteString("    " + line + "\n")
			} else {
				w.WriteString("        " + line + "\n")
			}
		}
	}
}

// writeCustomLocationBlock записывает кастомный location блок с правильным proxy_pass
func writeCustomLocationBlock(w stringWriter, customLocationStr string, correctUpstream string, resID int, protocol string, port int) {
	hasProxyPass := strings.Contains(customLocationStr, "proxy_pass")
	lines := strings.Split(customLocationStr, "\n")

	if hasProxyPass {
		for i, line := range lines {
			if strings.Contains(line, "proxy_pass") {
				trimmed := strings.TrimLeft(line, " \t")
				indent := line[:len(line)-len(trimmed)]
				lines[i] = indent + "proxy_pass " + correctUpstream + ";"
				log.Printf("    └─ Заменен proxy_pass в кастомном location: %s", correctUpstream)
			}
		}
	}

	var locationLines []string
	inLocationBlock := false
	for _, line := range lines {
		trimmed := strings.TrimSpace(line)
		if trimmed == "" {
			continue
		}

		if strings.HasPrefix(trimmed, "location ") {
			inLocationBlock = true
		}
		if trimmed == "}" {
			if !hasProxyPass && inLocationBlock {
				locationLines = append(locationLines, "        proxy_pass "+correctUpstream+";")
				log.Printf("    └─ Добавлен proxy_pass в кастомный location: %s", correctUpstream)
			}
			inLocationBlock = false
		}

		if strings.HasPrefix(line, "    ") || strings.HasPrefix(line, "\t") {
			locationLines = append(locationLines, line)
		} else {
			locationLines = append(locationLines, "    "+line)
		}
	}

	for _, line := range locationLines {
		w.WriteString(line + "\n")
	}
	w.WriteString("\n")
}
