package main

import (
	"context"
	"crypto/tls"
	"embed"
	"fmt"
	"html/template"
	"io"
	"io/fs"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"go-speech/handlers"
	"go-speech/internal/cache"
	"go-speech/internal/logger"
)

//go:embed static
var staticFiles embed.FS

//go:embed README.md
var readmeContent string

func main() {
	// Получение конфигурации из переменных окружения
	port := getEnv("GO_SPEECH_PORT", "8443")
	certFile := getEnv("GO_SPEECH_CERT_FILE", "certs/server.crt")
	keyFile := getEnv("GO_SPEECH_KEY_FILE", "certs/server.key")
	piperPath := getEnv("GO_SPEECH_PIPER_PATH", "/usr/local/bin/piper")
	ffmpegPath := getEnv("GO_SPEECH_FFMPEG_PATH", "/usr/bin/ffmpeg")

	// Получение пути к директории с моделями
	modelDir := getEnv("GO_SPEECH_MODEL_DIR", "models")

	// Определение директории кэша (рядом с исполняемым файлом)
	execPath, err := os.Executable()
	if err != nil {
		logger.Warn("Не удалось определить путь к исполняемому файлу: %v", err)
		execPath = "."
	}
	execDir := filepath.Dir(execPath)
	cacheDir := filepath.Join(execDir, "cache")
	logger.Debug("Директория исполняемого файла: %s", execDir)
	logger.Debug("Директория кэша: %s", cacheDir)

	// Инициализация кэша
	logger.Debug("Инициализация кэша...")
	cacheInstance, err := cache.NewCache(cacheDir)
	if err != nil {
		logger.Error("Ошибка инициализации кэша: %v", err)
		log.Fatalf("Ошибка инициализации кэша: %v", err)
	}

	// Запуск фоновой горутины для очистки кэша (каждые 5 минут, удаляем файлы старше 3 дней)
	cacheCleanupInterval := 5 * time.Minute
	cacheMaxAge := 3 * 24 * time.Hour
	cacheInstance.StartCleanupRoutine(cacheCleanupInterval, cacheMaxAge)

	// Детальное логирование конфигурации
	logger.Info("=== Запуск Go Speech TTS сервера ===")
	logger.Debug("Режим отладки: ВКЛЮЧЕН")
	logger.Debug("Конфигурация:")
	logger.Debug("  PORT: %s", port)
	logger.Debug("  CERT_FILE: %s", certFile)
	logger.Debug("  KEY_FILE: %s", keyFile)
	logger.Debug("  PIPER_PATH: %s", piperPath)
	logger.Debug("  FFMPEG_PATH: %s", ffmpegPath)
	logger.Debug("  MODEL_DIR: %s", modelDir)
	logger.Debug("  CACHE_DIR: %s", cacheDir)
	logger.Debug("  GO_SPEECH_VOICE: %s", getEnv("GO_SPEECH_VOICE", "ruslan"))
	logger.Debug("  GO_SPEECH_MODE: %s", getEnv("GO_SPEECH_MODE", "debug"))
	logger.Info("Директория с моделями: %s", modelDir)
	logger.Info("Директория кэша: %s", cacheDir)

	// Проверка наличия сертификатов
	logger.Debug("Проверка SSL сертификатов...")
	if _, err := os.Stat(certFile); os.IsNotExist(err) {
		logger.Error("SSL сертификат не найден: %s", certFile)
		log.Fatalf("SSL сертификат не найден: %s. Создайте сертификаты или укажите путь через CERT_FILE", certFile)
	}
	logger.Debug("SSL сертификат найден: %s", certFile)

	if _, err := os.Stat(keyFile); os.IsNotExist(err) {
		logger.Error("SSL ключ не найден: %s", keyFile)
		log.Fatalf("SSL ключ не найден: %s. Создайте ключ или укажите путь через KEY_FILE", keyFile)
	}
	logger.Debug("SSL ключ найден: %s", keyFile)

	// Загрузка TLS сертификатов
	logger.Debug("Загрузка TLS сертификатов...")
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		logger.Error("Ошибка загрузки TLS сертификатов: %v", err)
		log.Fatalf("Ошибка загрузки TLS сертификатов: %v", err)
	}
	logger.Debug("TLS сертификаты успешно загружены")

	// Настройка TLS конфигурации
	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{cert},
		MinVersion:   tls.VersionTLS12,
	}

	// Статические файлы для фронтенда
	logger.Debug("Загрузка статических файлов...")
	staticFS, err := fs.Sub(staticFiles, "static")
	if err != nil {
		logger.Error("Ошибка загрузки статических файлов: %v", err)
		log.Fatalf("Ошибка загрузки статических файлов: %v", err)
	}
	logger.Debug("Статические файлы успешно загружены")

	// Создание HTTP сервера
	mux := http.NewServeMux()

	// Инициализация TTS обработчика
	logger.Debug("Инициализация TTS обработчика...")
	ttsHandler := handlers.NewTTSHandler(piperPath, modelDir, ffmpegPath, cacheInstance)
	logger.Debug("Регистрация маршрутов:")

	// API endpoints
	mux.HandleFunc("/go-speech/api/v1/tts", ttsHandler.HandleTTS)
	logger.Debug("  POST /go-speech/api/v1/tts - синтез речи")
	mux.HandleFunc("/go-speech/api/v1/healthz", ttsHandler.HandleHealthz)
	logger.Debug("  GET /go-speech/api/v1/healthz - проверка TTS")
	mux.HandleFunc("/go-speech/api/v1/health", func(w http.ResponseWriter, r *http.Request) {
		logger.Debug("GET /go-speech/api/v1/health - проверка работоспособности")
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	})
	logger.Debug("  GET /go-speech/api/v1/health - проверка работоспособности")

	// Веб-интерфейс
	mux.HandleFunc("/go-speech/front", func(w http.ResponseWriter, r *http.Request) {
		logger.Debug("GET /go-speech/front - веб-интерфейс")
		if r.URL.Path != "/go-speech/front" {
			http.Redirect(w, r, "/go-speech/front", http.StatusMovedPermanently)
			return
		}
		indexFile, err := staticFS.Open("index.html")
		if err != nil {
			logger.Error("Ошибка открытия index.html: %v", err)
			http.Error(w, "Фронтенд не найден", http.StatusNotFound)
			return
		}
		defer indexFile.Close()
		w.Header().Set("Content-Type", "text/html; charset=utf-8")
		io.Copy(w, indexFile)
	})
	logger.Debug("  GET /go-speech/front - веб-интерфейс")

	// Обработчик для статических ресурсов фронтенда
	mux.Handle("/go-speech/front/", http.StripPrefix("/go-speech/front/", http.FileServer(http.FS(staticFS))))
	logger.Debug("  GET /go-speech/front/* - статические ресурсы")

	// Помощь - рендеринг README.md
	mux.HandleFunc("/go-speech/help", handleHelp)
	logger.Debug("  GET /go-speech/help - документация")

	server := &http.Server{
		Addr:         "0.0.0.0:" + port,
		Handler:      loggingMiddleware(mux),
		TLSConfig:    tlsConfig,
		ReadTimeout:  15 * time.Second,
		WriteTimeout: 30 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	logger.Debug("Настройки HTTP сервера:")
	logger.Debug("  ReadTimeout: %v", server.ReadTimeout)
	logger.Debug("  WriteTimeout: %v", server.WriteTimeout)
	logger.Debug("  IdleTimeout: %v", server.IdleTimeout)

	// Graceful shutdown
	go func() {
		// Создаем явный IPv4 listener
		logger.Debug("Создание IPv4 listener на 0.0.0.0:%s...", port)
		listener, err := net.Listen("tcp4", "0.0.0.0:"+port)
		if err != nil {
			logger.Error("Ошибка создания IPv4 listener: %v", err)
			log.Fatalf("Ошибка создания IPv4 listener: %v", err)
		}
		logger.Debug("IPv4 listener успешно создан: %v", listener.Addr())

		// Создаем TLS listener
		logger.Debug("Создание TLS listener...")
		tlsListener := tls.NewListener(listener, tlsConfig)
		logger.Debug("TLS listener успешно создан")

		logger.Info("Сервер запущен на https://0.0.0.0:%s (IPv4)", port)
		logger.Info("Простой веб-интерфейс доступен на https://localhost:%s/go-speech/front", port)
		logger.Info("Справка доступна по пути https://localhost:%s/go-speech/help", port)
		logger.Debug("Запуск server.Serve()...")

		if err := server.Serve(tlsListener); err != nil && err != http.ErrServerClosed {
			logger.Error("Ошибка запуска сервера: %v", err)
			log.Fatalf("Ошибка запуска сервера: %v", err)
		}
		logger.Debug("server.Serve() завершился")
	}()

	// Ожидание сигнала для graceful shutdown
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	logger.Debug("Ожидание сигнала для остановки (SIGINT/SIGTERM)...")
	sig := <-quit
	logger.Info("Получен сигнал: %v", sig)
	logger.Info("Остановка сервера...")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	logger.Debug("Graceful shutdown с таймаутом 5 секунд...")
	if err := server.Shutdown(ctx); err != nil {
		logger.Error("Ошибка при остановке сервера: %v", err)
		log.Fatalf("Ошибка при остановке сервера: %v", err)
	}

	logger.Info("Сервер остановлен")
}

// loggingMiddleware добавляет логирование запросов
func loggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		logger.Debug("Входящий запрос: %s %s от %s", r.Method, r.URL.Path, r.RemoteAddr)
		logger.Debug("  User-Agent: %s", r.UserAgent())
		logger.Debug("  Content-Type: %s", r.Header.Get("Content-Type"))
		logger.Debug("  Content-Length: %s", r.Header.Get("Content-Length"))

		next.ServeHTTP(w, r)

		duration := time.Since(start)
		logger.Info("%s %s %v", r.Method, r.URL.Path, duration)
		logger.Debug("Запрос обработан за %v", duration)
	})
}

// getEnv получает переменную окружения или возвращает значение по умолчанию
func getEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

// GetModelPath формирует путь к модели на основе выбранного голоса
func GetModelPath(modelDir, voice string) string {
	// Если указан полный путь через MODEL_PATH, используем его
	if modelPath := os.Getenv("MODEL_PATH"); modelPath != "" {
		return modelPath
	}

	// Формируем путь к модели на основе голоса
	modelFile := fmt.Sprintf("ru_RU-%s-medium.onnx", voice)
	return filepath.Join(modelDir, modelFile)
}

// handleHelp рендерит README.md в HTML
func handleHelp(w http.ResponseWriter, r *http.Request) {
	logger.Debug("GET /go-speech/help - отображение документации")

	// Простой рендеринг markdown в HTML
	html := markdownToHTML(readmeContent)

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte(html))
}

// markdownToHTML конвертирует markdown в простой HTML
func markdownToHTML(md string) string {
	html := `<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Go Speech - Документация</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
            max-width: 900px;
            margin: 0 auto;
            padding: 40px 20px;
            line-height: 1.6;
            color: #333;
            background: #f5f5f5;
        }
        h1, h2, h3 { color: #667eea; margin-top: 30px; }
        code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; }
        pre { background: #f4f4f4; padding: 15px; border-radius: 8px; overflow-x: auto; }
        pre code { background: none; padding: 0; }
        a { color: #667eea; text-decoration: none; }
        a:hover { text-decoration: underline; }
        blockquote { border-left: 4px solid #667eea; padding-left: 20px; margin-left: 0; color: #666; }
        table { border-collapse: collapse; width: 100%; margin: 20px 0; }
        th, td { border: 1px solid #ddd; padding: 12px; text-align: left; }
        th { background: #667eea; color: white; }
    </style>
</head>
<body>
`

	// Простая конвертация markdown в HTML
	lines := strings.Split(md, "\n")
	inCodeBlock := false
	inList := false
	var codeBlock strings.Builder

	for i, line := range lines {
		line = strings.TrimRight(line, "\r")
		trimmedLine := strings.TrimSpace(line)

		// Обработка блоков кода
		if strings.HasPrefix(trimmedLine, "```") {
			if inCodeBlock {
				// Закрываем блок кода
				html += "<pre><code>" + template.HTMLEscapeString(codeBlock.String()) + "</code></pre>\n"
				codeBlock.Reset()
				inCodeBlock = false
			} else {
				// Открываем блок кода
				inCodeBlock = true
			}
			if inList {
				html += "</ul>\n"
				inList = false
			}
			continue
		}

		if inCodeBlock {
			codeBlock.WriteString(line + "\n")
			continue
		}

		// Заголовки
		if strings.HasPrefix(trimmedLine, "# ") {
			if inList {
				html += "</ul>\n"
				inList = false
			}
			html += "<h1>" + template.HTMLEscapeString(strings.TrimPrefix(trimmedLine, "# ")) + "</h1>\n"
			continue
		}
		if strings.HasPrefix(trimmedLine, "## ") {
			if inList {
				html += "</ul>\n"
				inList = false
			}
			html += "<h2>" + template.HTMLEscapeString(strings.TrimPrefix(trimmedLine, "## ")) + "</h2>\n"
			continue
		}
		if strings.HasPrefix(trimmedLine, "### ") {
			if inList {
				html += "</ul>\n"
				inList = false
			}
			html += "<h3>" + template.HTMLEscapeString(strings.TrimPrefix(trimmedLine, "### ")) + "</h3>\n"
			continue
		}

		// Списки
		if strings.HasPrefix(trimmedLine, "- ") {
			if !inList {
				html += "<ul>\n"
				inList = true
			}
			content := strings.TrimPrefix(trimmedLine, "- ")
			// Обработка inline кода в списках
			content = processInlineCode(content)
			html += "<li>" + content + "</li>\n"
			continue
		}

		// Закрываем список если он был открыт
		if inList && trimmedLine == "" {
			html += "</ul>\n"
			inList = false
		}

		// Пустые строки
		if trimmedLine == "" {
			if i < len(lines)-1 {
				html += "<br>\n"
			}
			continue
		}

		// Обычный текст
		content := processInlineCode(template.HTMLEscapeString(trimmedLine))
		html += "<p>" + content + "</p>\n"
	}

	// Закрываем открытые теги
	if inList {
		html += "</ul>\n"
	}

	html += `</body>
</html>`
	return html
}

// processInlineCode обрабатывает inline код в markdown
func processInlineCode(text string) string {
	// Простая обработка inline кода `code`
	parts := strings.Split(text, "`")
	result := strings.Builder{}
	for i, part := range parts {
		if i%2 == 0 {
			result.WriteString(part)
		} else {
			result.WriteString("<code>")
			result.WriteString(part)
			result.WriteString("</code>")
		}
	}
	return result.String()
}
