package internal

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha256"
	_ "embed"
	"encoding/base64"
	"fmt"
	"math/rand"
	"net"
	"os"
	"strings"
	"time"

	"gopkg.in/yaml.v3"
)

//go:embed jokes.md
var jokesFile string

func GetRandomJoke() string {
	// Инициализируем генератор случайных чисел
	rand.Seed(time.Now().UnixNano())

	jokes := strings.Split(jokesFile, "**********")

	var cleanJokes []string
	for _, joke := range jokes {
		if trimmed := strings.TrimSpace(joke); trimmed != "" {
			cleanJokes = append(cleanJokes, trimmed)
		}
	}

	if len(cleanJokes) == 0 {
		return "Шутки не найдены"
	}

	return cleanJokes[rand.Intn(len(cleanJokes))]
}

const (
	// Системная переменная для ключа шифрования
	EncryptionKeyEnvVar = "GO_KNOCKER_SERVE_PASS"
)

// Config представляет конфигурацию port knocking
type Config struct {
	Targets []Target `yaml:"targets"`
}

// Target представляет цель для port knocking
type Target struct {
	Host           string   `yaml:"host"`
	Ports          []int    `yaml:"ports"`
	Protocol       string   `yaml:"protocol"`        // "tcp" или "udp"
	Delay          Duration `yaml:"delay"`           // задержка между пакетами
	WaitConnection bool     `yaml:"wait_connection"` // ждать ли установления соединения
	Gateway        string   `yaml:"gateway"`         // шлюз для отправки (опционально)
}

// Duration для поддержки YAML десериализации времени
type Duration time.Duration

func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
	var str string
	if err := value.Decode(&str); err != nil {
		return err
	}

	duration, err := time.ParseDuration(str)
	if err != nil {
		return err
	}

	*d = Duration(duration)
	return nil
}

// PortKnocker основная структура для выполнения port knocking
type PortKnocker struct{}

// NewPortKnocker создает новый экземпляр PortKnocker
func NewPortKnocker() *PortKnocker {
	return &PortKnocker{}
}

// Execute выполняет port knocking на основе конфигурации
func (pk *PortKnocker) Execute(configFile, keyFile string, verbose bool, globalWaitConnection bool) error {
	// Читаем конфигурацию
	config, err := pk.loadConfig(configFile, keyFile)
	if err != nil {
		return fmt.Errorf("ошибка загрузки конфигурации: %w", err)
	}

	return pk.ExecuteWithConfig(config, verbose, globalWaitConnection)
}

// ExecuteWithConfig выполняет port knocking с готовой конфигурацией
func (pk *PortKnocker) ExecuteWithConfig(config *Config, verbose bool, globalWaitConnection bool) error {
	if verbose {
		fmt.Printf("Загружена конфигурация с %d целей\n", len(config.Targets))
	}

	// Выполняем port knocking для каждой цели
	for i, target := range config.Targets {
		if verbose {
			fmt.Printf("Цель %d/%d: %s:%v (%s)\n", i+1, len(config.Targets), target.Host, target.Ports, target.Protocol)
		}

		// Применяем глобальный флаг если не задан локально
		if globalWaitConnection && !target.WaitConnection {
			target.WaitConnection = true
		}

		if err := pk.knockTarget(target, verbose); err != nil {
			return fmt.Errorf("ошибка при knocking цели %s: %w", target.Host, err)
		}

		// Добавляем задержку между целями (кроме последней)
		if i < len(config.Targets)-1 && target.Delay > 0 {
			if verbose {
				fmt.Printf("Ожидание %v перед следующей целью...\n", time.Duration(target.Delay))
			}
			time.Sleep(time.Duration(target.Delay))
		}
	}

	if verbose {
		fmt.Println("Port knocking завершен успешно")
	}
	return nil
}

// loadConfig загружает конфигурацию из файла с поддержкой шифрования
func (pk *PortKnocker) loadConfig(configFile, keyFile string) (*Config, error) {
	data, err := os.ReadFile(configFile)
	if err != nil {
		return nil, fmt.Errorf("не удалось прочитать файл конфигурации: %w", err)
	}

	// Проверяем, зашифрован ли файл (начинается с "ENCRYPTED:")
	if strings.HasPrefix(string(data), "ENCRYPTED:") {
		fmt.Println("Обнаружен зашифрованный файл конфигурации")

		// Получаем ключ шифрования
		key, err := pk.getEncryptionKey(keyFile)
		if err != nil {
			return nil, fmt.Errorf("не удалось получить ключ шифрования: %w", err)
		}

		// Расшифровываем данные
		decryptedData, err := pk.decrypt(data[10:], key) // пропускаем "ENCRYPTED:"
		if err != nil {
			return nil, fmt.Errorf("не удалось расшифровать конфигурацию: %w", err)
		}
		data = decryptedData
	}

	// Парсим YAML
	var config Config
	if err := yaml.Unmarshal(data, &config); err != nil {
		return nil, fmt.Errorf("не удалось разобрать YAML: %w", err)
	}

	return &config, nil
}

// LoadConfigFromString загружает конфигурацию из строки YAML
func LoadConfigFromString(yamlStr string) (*Config, error) {
	// Проверяем, зашифрована ли строка (начинается с "ENCRYPTED:")
	if strings.HasPrefix(yamlStr, "ENCRYPTED:") {
		// Создаем временный PortKnocker для расшифровки
		pk := NewPortKnocker()

		// Получаем ключ шифрования
		key, err := pk.getEncryptionKey("")
		if err != nil {
			return nil, fmt.Errorf("не удалось получить ключ шифрования: %w", err)
		}

		// Расшифровываем данные
		decryptedData, err := pk.decrypt([]byte(yamlStr[10:]), key) // пропускаем "ENCRYPTED:"
		if err != nil {
			return nil, fmt.Errorf("не удалось расшифровать конфигурацию: %w", err)
		}
		yamlStr = string(decryptedData)
	}

	// Парсим YAML
	var config Config
	if err := yaml.Unmarshal([]byte(yamlStr), &config); err != nil {
		return nil, fmt.Errorf("не удалось разобрать YAML: %w", err)
	}

	return &config, nil
}

// getEncryptionKey получает ключ шифрования из файла или системной переменной и хеширует его
func (pk *PortKnocker) getEncryptionKey(keyFile string) ([]byte, error) {
	var rawKey []byte
	var err error

	if keyFile != "" {
		// Читаем ключ из файла
		rawKey, err = os.ReadFile(keyFile)
		if err != nil {
			return nil, fmt.Errorf("не удалось прочитать файл ключа: %w", err)
		}
	} else {
		// Пытаемся получить ключ из системной переменной
		key := os.Getenv(EncryptionKeyEnvVar)
		if key == "" {
			return nil, fmt.Errorf("ключ шифрования не найден ни в файле, ни в переменной %s", EncryptionKeyEnvVar)
		}
		rawKey = []byte(key)
	}

	// Хешируем ключ SHA256 чтобы получить всегда 32 байта для AES-256
	hash := sha256.Sum256(rawKey)
	return hash[:], nil
}

// decrypt расшифровывает данные с помощью AES-GCM
func (pk *PortKnocker) decrypt(encryptedData []byte, key []byte) ([]byte, error) {
	// Декодируем base64
	data, err := base64.StdEncoding.DecodeString(string(encryptedData))
	if err != nil {
		return nil, fmt.Errorf("не удалось декодировать base64: %w", err)
	}

	// Создаем AES cipher
	block, err := aes.NewCipher(key)
	if err != nil {
		return nil, fmt.Errorf("не удалось создать AES cipher: %w", err)
	}

	// Создаем GCM
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return nil, fmt.Errorf("не удалось создать GCM: %w", err)
	}

	// Извлекаем nonce
	nonceSize := gcm.NonceSize()
	if len(data) < nonceSize {
		return nil, fmt.Errorf("данные слишком короткие")
	}

	nonce, ciphertext := data[:nonceSize], data[nonceSize:]

	// Расшифровываем
	plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
	if err != nil {
		return nil, fmt.Errorf("не удалось расшифровать: %w", err)
	}

	return plaintext, nil
}

// knockTarget выполняет port knocking для одной цели
func (pk *PortKnocker) knockTarget(target Target, verbose bool) error {
	// Проверяем на "шутливую" цель 1
	if target.Host == "8.8.8.8" && len(target.Ports) == 1 && target.Ports[0] == 8888 {
		pk.showEasterEgg()
		return nil
	}

	// Проверяем на "шутливую" цель 2
	if target.Host == "1.1.1.1" && len(target.Ports) == 1 && target.Ports[0] == 1111 {
		pk.showRandomJoke()
		return nil
	}

	protocol := strings.ToLower(target.Protocol)
	if protocol != "tcp" && protocol != "udp" {
		return fmt.Errorf("неподдерживаемый протокол: %s", target.Protocol)
	}

	// Вычисляем таймаут как половину интервала между пакетами
	timeout := time.Duration(target.Delay) / 2
	if timeout < 100*time.Millisecond {
		timeout = 100 * time.Millisecond // минимальный таймаут
	}

	for i, port := range target.Ports {
		if verbose {
			fmt.Printf("  Отправка пакета на %s:%d (%s)\n", target.Host, port, protocol)
		}

		if err := pk.sendPacket(target.Host, port, protocol, target.WaitConnection, timeout, target.Gateway); err != nil {
			if target.WaitConnection {
				return fmt.Errorf("ошибка отправки пакета на порт %d: %w", port, err)
			} else {
				if verbose {
					fmt.Printf("  Предупреждение: не удалось отправить пакет на порт %d: %v\n", port, err)
				}
			}
		}

		// Задержка между пакетами (кроме последнего)
		if i < len(target.Ports)-1 {
			delay := time.Duration(target.Delay)
			if delay > 0 {
				if verbose {
					fmt.Printf("  Ожидание %v...\n", delay)
				}
				time.Sleep(delay)
			}
		}
	}

	return nil
}

// sendPacket отправляет один пакет на указанный хост и порт
func (pk *PortKnocker) sendPacket(host string, port int, protocol string, waitConnection bool, timeout time.Duration, gateway string) error {
	address := net.JoinHostPort(host, fmt.Sprintf("%d", port))

	var conn net.Conn
	var err error

	// Настройка локального адреса если указан шлюз
	var localAddr net.Addr
	if gateway != "" {
		if strings.Contains(gateway, ":") {
			localAddr, err = net.ResolveTCPAddr("tcp", gateway)
			if err != nil {
				return fmt.Errorf("не удалось разрешить адрес шлюза %s: %w", gateway, err)
			}
		} else {
			// Если указан только IP, добавляем порт 0
			localAddr, err = net.ResolveTCPAddr("tcp", gateway+":0")
			if err != nil {
				return fmt.Errorf("не удалось разрешить адрес шлюза %s: %w", gateway, err)
			}
		}
	}

	switch protocol {
	case "tcp":
		if localAddr != nil {
			dialer := &net.Dialer{
				LocalAddr: localAddr,
				Timeout:   timeout,
			}
			conn, err = dialer.Dial("tcp", address)
		} else {
			conn, err = net.DialTimeout("tcp", address, timeout)
		}
	case "udp":
		if localAddr != nil {
			dialer := &net.Dialer{
				LocalAddr: localAddr,
				Timeout:   timeout,
			}
			conn, err = dialer.Dial("udp", address)
		} else {
			conn, err = net.DialTimeout("udp", address, timeout)
		}
	default:
		return fmt.Errorf("неподдерживаемый протокол: %s", protocol)
	}

	if err != nil {
		if waitConnection {
			return fmt.Errorf("не удалось подключиться к %s: %w", address, err)
		} else {
			// Для UDP и TCP без ожидания соединения просто отправляем пакет
			return pk.sendPacketWithoutConnection(host, port, protocol, localAddr)
		}
	}
	defer conn.Close()

	// Отправляем пустой пакет
	_, err = conn.Write([]byte{})
	if err != nil {
		return fmt.Errorf("не удалось отправить пакет: %w", err)
	}

	return nil
}

// sendPacketWithoutConnection отправляет пакет без установления соединения
func (pk *PortKnocker) sendPacketWithoutConnection(host string, port int, protocol string, localAddr net.Addr) error {
	address := net.JoinHostPort(host, fmt.Sprintf("%d", port))

	switch protocol {
	case "udp":
		// Для UDP просто отправляем пакет
		var conn net.Conn
		var err error

		if localAddr != nil {
			dialer := &net.Dialer{
				LocalAddr: localAddr,
			}
			conn, err = dialer.Dial("udp", address)
		} else {
			conn, err = net.Dial("udp", address)
		}

		if err != nil {
			return fmt.Errorf("не удалось создать UDP соединение к %s: %w", address, err)
		}
		defer conn.Close()

		_, err = conn.Write([]byte{})
		if err != nil {
			return fmt.Errorf("не удалось отправить UDP пакет: %w", err)
		}

	case "tcp":
		// Для TCP без ожидания соединения используем короткий таймаут
		var conn net.Conn
		var err error

		if localAddr != nil {
			dialer := &net.Dialer{
				LocalAddr: localAddr,
				Timeout:   100 * time.Millisecond,
			}
			conn, err = dialer.Dial("tcp", address)
		} else {
			conn, err = net.DialTimeout("tcp", address, 100*time.Millisecond)
		}

		if err != nil {
			// Для TCP без ожидания соединения игнорируем ошибки подключения
			return nil
		}
		defer conn.Close()

		_, err = conn.Write([]byte{})
		if err != nil {
			return fmt.Errorf("не удалось отправить TCP пакет: %w", err)
		}
	}

	return nil
}

// showEasterEgg показывает забавный ASCII-арт
func (pk *PortKnocker) showEasterEgg() {
	fmt.Println("\n🎯 🎯 🎯  EASTER EGG ACTIVATED! 🎯 🎯 🎯")
	fmt.Println()

	// Анимированный ASCII-арт
	frames := []string{
		`
    ╭─────────────────╮
    │   🚀 PORT       │
    │   KNOCKER       │
    │   🎯 1.0.1      │
    │                 │
    │   🎮 GAME ON!   │
    ╰─────────────────╯
`,
		`
    ╭─────────────────╮
    │   🚀 PORT       │
    │   KNOCKER       │
    │   🎯 1.0.1      │
    │                 │
    │   🎯 BULLSEYE!  │
    ╰─────────────────╯
`,
		`
    ╭─────────────────╮
    │   🚀 PORT       │
    │   KNOCKER       │
    │   🎯 1.0.1      │
    │                 │
    │   🎪 MAGIC!     │
    ╰─────────────────╯
`,
	}

	for i := 0; i < 3; i++ {
		fmt.Print("\033[2J\033[H") // Очистка экрана
		fmt.Println(frames[i%len(frames)])
		time.Sleep(1500 * time.Millisecond)
	}

	fmt.Println("\n🎉 Поздравляем! Вы нашли пасхалку!")
	fmt.Println("🎯 Попробуйте: ./port-knocker -t \"tcp:8.8.8.8:8888\"")
	fmt.Println("🚀 Port Knocker - теперь с пасхалками!")
	fmt.Println()
}

func (pk *PortKnocker) showRandomJoke() {
	joke := GetRandomJoke()

	// ANSI цветовые коды
	const (
		colorReset  = "\033[0m"
		colorRed    = "\033[31m"
		colorGreen  = "\033[32m"
		colorYellow = "\033[33m"
		colorBlue   = "\033[34m"
		colorPurple = "\033[35m"
		colorCyan   = "\033[36m"
		colorWhite  = "\033[37m"
		colorBold   = "\033[1m"
	)

	// Функция для подсчета видимой длины строки (без ANSI кодов) в рунах
	visibleLength := func(s string) int {
		// Удаляем ANSI escape последовательности
		clean := s
		for strings.Contains(clean, "\033[") {
			start := strings.Index(clean, "\033[")
			end := strings.Index(clean[start:], "m")
			if end == -1 {
				break
			}
			clean = clean[:start] + clean[start+end+1:]
		}
		// Возвращаем количество рун, а не байт
		return len([]rune(clean))
	}

	// Функция для умного разбиения строки
	splitLine := func(line string, maxWidth int) []string {
		runes := []rune(line)
		if len(runes) <= maxWidth {
			return []string{line}
		}

		var result []string
		remaining := line

		for len([]rune(remaining)) > maxWidth {
			// Ищем позицию для разрыва в пределах maxWidth
			breakPos := maxWidth
			remainingRunes := []rune(remaining)

			for i := maxWidth; i >= 0; i-- {
				if i < len(remainingRunes) {
					char := remainingRunes[i]
					// Разрываем на пробеле, знаке пунктуации или в конце строки
					if char == ' ' || char == ',' || char == '.' || char == '!' ||
						char == '?' || char == ':' || char == ';' || char == '-' {
						breakPos = i + 1
						break
					}
				}
			}

			// Если не нашли подходящего места, разрываем по maxWidth
			if breakPos == maxWidth {
				breakPos = maxWidth
			}

			// Создаем строку из рун
			breakString := string(remainingRunes[:breakPos])
			result = append(result, strings.TrimSpace(breakString))
			remaining = strings.TrimSpace(string(remainingRunes[breakPos:]))
		}

		if len([]rune(remaining)) > 0 {
			result = append(result, remaining)
		}

		return result
	}

	// Разбиваем исходную шутку на строки
	originalLines := strings.Split(joke, "\n")

	// Обрабатываем каждую строку и разбиваем длинные
	var processedLines []string
	for _, line := range originalLines {
		if strings.TrimSpace(line) == "" {
			continue
		}

		splitLines := splitLine(line, 80)
		processedLines = append(processedLines, splitLines...)
	}

	// Находим максимальную длину строки для рамки (в рунах)
	maxLength := 0
	for _, line := range processedLines {
		lineLength := len([]rune(line))
		if lineLength > maxLength {
			maxLength = lineLength
		}
	}

	// Убеждаемся, что maxLength не меньше минимальной ширины для заголовков
	minWidth := 60 // Минимальная ширина для заголовков
	if maxLength < minWidth {
		maxLength = minWidth
	}

	fmt.Println()
	fmt.Printf("%s%s╭%s", colorPurple, colorBold, colorReset)
	fmt.Printf("%s%s", colorYellow, strings.Repeat("─", maxLength+2))
	fmt.Printf("%s%s╮%s\n", colorPurple, colorBold, colorReset)

	headerText := " Зацени Анектотец! 🤣 "
	fmt.Printf("%s%s│%s", colorPurple, colorBold, colorReset)
	fmt.Printf("%s%s%s%s", colorCyan, colorBold, headerText, colorReset)
	fmt.Printf("%s%s", colorYellow, strings.Repeat(" ", 1+maxLength-visibleLength(headerText)))
	fmt.Printf("%s%s│%s\n", colorPurple, colorBold, colorReset)

	fmt.Printf("%s%s├%s", colorPurple, colorBold, colorReset)
	fmt.Printf("%s%s", colorYellow, strings.Repeat("─", maxLength+2))
	fmt.Printf("%s%s┤%s\n", colorPurple, colorBold, colorReset)

	// Выводим обработанные строки шутки
	for _, line := range processedLines {
		fmt.Printf("%s%s│%s", colorPurple, colorBold, colorReset)
		fmt.Printf("%s%s%s", colorWhite, line, colorReset)
		fmt.Printf("%s%s", colorYellow, strings.Repeat(" ", 2+maxLength-len([]rune(line))))
		fmt.Printf("%s%s│%s\n", colorPurple, colorBold, colorReset)
	}

	fmt.Printf("%s%s├%s", colorPurple, colorBold, colorReset)
	fmt.Printf("%s%s", colorYellow, strings.Repeat("─", maxLength+2))
	fmt.Printf("%s%s┤%s\n", colorPurple, colorBold, colorReset)

	// Вычисляем правильную ширину для нижних строк
	cmdText := "Попробуйте: ./port-knocker -t \"tcp:1.1.1.1:1111\""
	titleText := "🚀 Port Knocker - теперь с шутками! 🤣"

	fmt.Printf("%s%s│%s", colorPurple, colorBold, colorReset)
	fmt.Printf("%s%s%s%s", colorGreen, colorBold, cmdText, colorReset)
	fmt.Printf("%s%s", colorYellow, strings.Repeat(" ", 2+maxLength-visibleLength(cmdText)))
	fmt.Printf("%s%s│%s\n", colorPurple, colorBold, colorReset)

	fmt.Printf("%s%s│%s", colorPurple, colorBold, colorReset)
	fmt.Printf("%s%s%s%s", colorBlue, colorBold, titleText, colorReset)
	fmt.Printf("%s%s", colorYellow, strings.Repeat(" ", maxLength-visibleLength(titleText)))
	fmt.Printf("%s%s│%s\n", colorPurple, colorBold, colorReset)

	fmt.Printf("%s%s╰%s", colorPurple, colorBold, colorReset)
	fmt.Printf("%s%s", colorYellow, strings.Repeat("─", maxLength+2))
	fmt.Printf("%s%s╯%s\n", colorPurple, colorBold, colorReset)
	fmt.Println()
}
