package main

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"
	"time"
)

// apiClient — переиспользуемый HTTP-клиент для всех API запросов.
// http.Client безопасен для конкурентного использования и переиспользует соединения.
var apiClient = &http.Client{
	Timeout: 120 * time.Second,
}

// makeAPIRequest выполняет GET запрос к API с авторизацией и повторными попытками
func makeAPIRequest(url, token string) ([]byte, error) {
	maxRetries := 3
	var lastErr error

	for attempt := 1; attempt <= maxRetries; attempt++ {
		body, err := doAPIRequest(url, token)
		if err == nil {
			if attempt > 1 {
				log.Printf("  ✓ Запрос успешен после %d попыток", attempt)
			}
			return body, nil
		}

		lastErr = err
		if attempt < maxRetries {
			waitTime := time.Duration(attempt*attempt) * time.Second // 1s, 4s, 9s
			log.Printf("  ⚠ Попытка %d/%d не удалась: %v, повтор через %v", attempt, maxRetries, err, waitTime)
			time.Sleep(waitTime)
		}
	}

	return nil, fmt.Errorf("запрос не удался после %d попыток: %w", maxRetries, lastErr)
}

// doAPIRequest выполняет одиночный API запрос (без retry)
func doAPIRequest(url, token string) ([]byte, error) {
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, fmt.Errorf("ошибка создания запроса: %w", err)
	}

	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")

	resp, err := apiClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("ошибка чтения ответа: %w", err)
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("API вернул код %d, URL: %s, ответ: %s", resp.StatusCode, url, string(body))
	}

	return body, nil
}

// getResourceDataFromCache пытается получить данные из таблицы sp_info
func getResourceDataFromCache(db *sql.DB, config Config, l7ResourceID int) (serverName string, aliases []string, origins []OriginItem, found bool, err error) {
	query := `
		SELECT 
			domain_name, 
			aliases, 
			origins, 
			updated_at,
			EXTRACT(EPOCH FROM (NOW() - updated_at)) / 60 AS age_minutes
		FROM sp_info
		WHERE sid = $1
	`

	var domainName sql.NullString
	var aliasesJSON sql.NullString
	var originsJSON sql.NullString
	var updatedAt sql.NullTime
	var ageMinutes sql.NullFloat64

	err = db.QueryRow(query, l7ResourceID).Scan(&domainName, &aliasesJSON, &originsJSON, &updatedAt, &ageMinutes)
	if err != nil {
		if err == sql.ErrNoRows {
			return "", nil, nil, false, nil // Данных нет в кеше
		}
		return "", nil, nil, false, fmt.Errorf("ошибка чтения из sp_info: %w", err)
	}

	// Проверяем свежесть данных (не старее 10 минут)
	if !ageMinutes.Valid {
		log.Printf("  ⚠ updated_at не заполнен в кеше, обращаюсь к API")
		return "", nil, nil, false, nil
	}

	age := ageMinutes.Float64
	if age < 0 {
		age = 0
	}

	if age > 10 {
		log.Printf("  ⚠ Данные в кеше устарели (возраст: %.1f мин), обращаюсь к API", age)
		return "", nil, nil, false, nil
	}

	log.Printf("  ✓ Данные из кеша актуальны (возраст: %.1f мин)", age)

	if domainName.Valid {
		serverName = domainName.String
	}

	aliases, err = parseAliasesJSON(aliasesJSON)
	if err != nil {
		return "", nil, nil, false, fmt.Errorf("ошибка парсинга aliases из sp_info: %w", err)
	}

	origins, err = parseOriginsJSON(originsJSON, config.WAFNetworks)
	if err != nil {
		return "", nil, nil, false, fmt.Errorf("ошибка парсинга origins из sp_info: %w", err)
	}

	return serverName, aliases, origins, true, nil
}

// getResourceDataFromManualInfo получает данные из таблицы manual_info (ручное управление)
// В отличие от sp_info, не проверяет timestamp - данные всегда актуальны
func getResourceDataFromManualInfo(db *sql.DB, config Config, l7ResourceID int) (serverName string, aliases []string, origins []OriginItem, err error) {
	query := `
		SELECT 
			domain_name, 
			aliases, 
			origins
		FROM manual_info
		WHERE sid = $1
	`

	var domainName sql.NullString
	var aliasesJSON sql.NullString
	var originsJSON sql.NullString

	err = db.QueryRow(query, l7ResourceID).Scan(&domainName, &aliasesJSON, &originsJSON)
	if err != nil {
		if err == sql.ErrNoRows {
			return "", nil, nil, fmt.Errorf("данные не найдены в manual_info для sid=%d", l7ResourceID)
		}
		return "", nil, nil, fmt.Errorf("ошибка чтения из manual_info: %w", err)
	}

	if domainName.Valid {
		serverName = domainName.String
	}

	aliases, err = parseAliasesJSON(aliasesJSON)
	if err != nil {
		return "", nil, nil, fmt.Errorf("ошибка парсинга aliases из manual_info: %w", err)
	}

	origins, err = parseOriginsJSON(originsJSON, config.WAFNetworks)
	if err != nil {
		return "", nil, nil, fmt.Errorf("ошибка парсинга origins из manual_info: %w", err)
	}

	return serverName, aliases, origins, nil
}

// parseAliasesJSON парсит JSON массив алиасов из sql.NullString
func parseAliasesJSON(aliasesJSON sql.NullString) ([]string, error) {
	if !aliasesJSON.Valid || aliasesJSON.String == "" || aliasesJSON.String == "null" {
		return nil, nil
	}

	var aliases []string
	if err := json.Unmarshal([]byte(aliasesJSON.String), &aliases); err != nil {
		return nil, err
	}
	return aliases, nil
}

// parseOriginsJSON парсит JSON массив origins из sql.NullString, фильтруя WAF IP
func parseOriginsJSON(originsJSON sql.NullString, wafNetworks []string) ([]OriginItem, error) {
	if !originsJSON.Valid || originsJSON.String == "" {
		return nil, nil
	}

	var originsData []struct {
		IP     string `json:"ip"`
		Mode   string `json:"mode"`
		Weight int    `json:"weight"`
	}
	if err := json.Unmarshal([]byte(originsJSON.String), &originsData); err != nil {
		return nil, err
	}

	var origins []OriginItem
	for _, o := range originsData {
		if !isIPInWAFNetworks(o.IP, wafNetworks) {
			origins = append(origins, OriginItem{
				IP:     o.IP,
				Weight: o.Weight,
				Mode:   o.Mode,
			})
		}
	}
	return origins, nil
}

// getServerName получает имя сервера (l7ResourceName) через API
func getServerName(config Config, l7ResourceID int) (string, error) {
	url := fmt.Sprintf("https://api.servicepipe.ru/api/v1/l7/resource/%d/global", l7ResourceID)
	body, err := makeAPIRequest(url, config.APIToken)
	if err != nil {
		return "", err
	}

	var response ResourceResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return "", fmt.Errorf("ошибка парсинга JSON: %w", err)
	}

	return response.Data.Result.L7ResourceName, nil
}

// getAliases получает список aliases через API
func getAliases(config Config, l7ResourceID int) ([]string, error) {
	url := fmt.Sprintf("https://api.servicepipe.ru/api/v1/l7/alias/global?limit=1000&l7ResourceId=%d", l7ResourceID)
	body, err := makeAPIRequest(url, config.APIToken)
	if err != nil {
		return nil, err
	}

	var response AliasResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, fmt.Errorf("ошибка парсинга JSON: %w", err)
	}

	var aliases []string
	for _, item := range response.Data.Result.Items {
		aliases = append(aliases, item.Domain)
	}

	return aliases, nil
}

// getOrigins получает список origins через API, исключая WAF IP
func getOrigins(config Config, l7ResourceID int) ([]OriginItem, error) {
	url := fmt.Sprintf("https://api.servicepipe.ru/api/v1/l7/origin/global?limit=1000&l7ResourceId=%d", l7ResourceID)
	body, err := makeAPIRequest(url, config.APIToken)
	if err != nil {
		return nil, err
	}

	var response OriginResponse
	if err := json.Unmarshal(body, &response); err != nil {
		return nil, fmt.Errorf("ошибка парсинга JSON: %w", err)
	}

	var origins []OriginItem
	for _, item := range response.Data.Result.Items {
		if !isIPInWAFNetworks(item.IP, config.WAFNetworks) {
			origins = append(origins, OriginItem{
				IP:     item.IP,
				Weight: item.Weight,
				Mode:   item.Mode,
			})
		}
	}

	return origins, nil
}

// getInputHTTPPorts возвращает список HTTP INPUT портов из AppsSettings или дефолтные
func getInputHTTPPorts(settings AppsSettings) []int {
	if len(settings.CustomInputHTTPPorts) > 0 {
		return int64ArrayToIntSlice(settings.CustomInputHTTPPorts)
	}
	return []int{80}
}

// getInputHTTPSPorts возвращает список HTTPS INPUT портов из AppsSettings или дефолтные
func getInputHTTPSPorts(settings AppsSettings) []int {
	if len(settings.CustomInputHTTPSPorts) > 0 {
		return int64ArrayToIntSlice(settings.CustomInputHTTPSPorts)
	}
	return []int{443}
}

// getOutputHTTPPorts возвращает список HTTP OUTPUT портов из AppsSettings или дефолтные
func getOutputHTTPPorts(settings AppsSettings) []int {
	if len(settings.CustomOutputHTTPPorts) > 0 {
		return int64ArrayToIntSlice(settings.CustomOutputHTTPPorts)
	}
	return []int{80}
}

// getOutputHTTPSPorts возвращает список HTTPS OUTPUT портов из AppsSettings или дефолтные
func getOutputHTTPSPorts(settings AppsSettings) []int {
	if len(settings.CustomOutputHTTPSPorts) > 0 {
		return int64ArrayToIntSlice(settings.CustomOutputHTTPSPorts)
	}
	return []int{443}
}

// int64ArrayToIntSlice конвертирует pq.Int64Array в []int
func int64ArrayToIntSlice(arr []int64) []int {
	result := make([]int, len(arr))
	for i, v := range arr {
		result[i] = int(v)
	}
	return result
}

// writeCustomUpstreamOrDefault записывает кастомные или дефолтные upstream-директивы
func writeCustomUpstreamOrDefault(w stringWriter, customUpstream sql.NullString, sni sql.NullInt64) {
	hasCustomUpstream := customUpstream.Valid && customUpstream.String != ""

	if hasCustomUpstream {
		w.WriteString("\n    # Custom upstream directives (replace defaults)\n")
		lines := strings.Split(strings.TrimSpace(customUpstream.String), "\n")
		for _, line := range lines {
			trimmed := strings.TrimSpace(line)
			if trimmed != "" {
				w.WriteString("    " + trimmed + "\n")
			}
		}
	} else {
		// Если нет custom - используем дефолтные keepalive директивы (только если sni != 1)
		if !sni.Valid || sni.Int64 != 1 {
			w.WriteString("\n    # Default upstream directives\n")
			w.WriteString("    keepalive 60;\n")
			w.WriteString("    keepalive_timeout 70s;\n")
		}
	}
}

// stringWriter — интерфейс для записи строк (совпадает с *os.File и *strings.Builder)
type stringWriter interface {
	WriteString(s string) (int, error)
}

// getMaxFails возвращает max_fails из AppsSettings или дефолтное значение 5
func getMaxFails(settings AppsSettings) int {
	if settings.MaxFails.Valid && settings.MaxFails.Int64 >= 0 {
		return int(settings.MaxFails.Int64)
	}
	return 5 // дефолт
}

// getFailTimeout возвращает fail_timeout из AppsSettings или дефолтное значение 15
func getFailTimeout(settings AppsSettings) int {
	if settings.FailTimeout.Valid && settings.FailTimeout.Int64 >= 0 {
		return int(settings.FailTimeout.Int64)
	}
	return 15 // дефолт
}
