package main

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"text/template"
	"time"
)

func recreateContainer(containerFullName, composeFile, containerDir, clientTitle string, resources []ResourceData, resourcePortMap map[int][]PortMapping, containerNum int) {
	log.Printf("  docker-compose.yml изменился, пересоздаём контейнер %s...", containerFullName)
	portsToFree := make([]int, 0)
	for _, res := range resources {
		if containerPorts, ok := resourcePortMap[res.L7ResourceID]; ok {
			if containerNum-1 < len(containerPorts) {
				for _, dockerPort := range containerPorts[containerNum-1].HTTPPorts {
					portsToFree = append(portsToFree, dockerPort)
				}
				for _, dockerPort := range containerPorts[containerNum-1].HTTPSPorts {
					portsToFree = append(portsToFree, dockerPort)
				}
			}
		}
	}
	log.Printf("  → Останавливаем старый контейнер...")
	cmd := exec.Command("docker-compose", "-f", composeFile, "down")
	cmd.Dir = containerDir
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Printf("  ⚠ Ошибка остановки контейнера %s: %v, output: %s", containerFullName, err, string(output))
	}

	log.Printf("  → Ожидаем остановки контейнера...")
	containerStillExists := false
	for i := 0; i < 30; i++ {
		exists, _ := checkContainerStatus(containerFullName)
		if !exists {
			break
		}
		containerStillExists = true
		time.Sleep(1 * time.Second)
	}
	if containerStillExists {
		exists, _ := checkContainerStatus(containerFullName)
		if exists {
			log.Printf("  → Контейнер не остановился, принудительное удаление...")
			if err := forceRemoveContainer(containerFullName); err != nil {
				log.Printf("  ⚠ Не удалось принудительно удалить контейнер: %v", err)
				return
			}
		}
	}

	if len(portsToFree) > 0 {
		log.Printf("  → Ожидаем освобождения %d портов...", len(portsToFree))
		allPortsFree := false
		for attempt := 0; attempt < 180; attempt++ { // Увеличено с 60 до 180 секунд
			usedPorts, err := getUsedPorts()
			if err != nil {
				log.Printf("  ⚠ Ошибка проверки портов: %v", err)
				time.Sleep(1 * time.Second)
				continue
			}
			portsBusy := 0
			busyPortsList := make([]int, 0)
			for _, port := range portsToFree {
				if usedPorts[port] {
					portsBusy++
					busyPortsList = append(busyPortsList, port)
				}
			}
			if portsBusy == 0 {
				allPortsFree = true
				log.Printf("  ✓ Все порты освобождены (попытка %d)", attempt+1)
				break
			}
			if attempt%5 == 0 && attempt > 0 {
				log.Printf("  → Ещё заняты %d портов: %v (попытка %d/180)", portsBusy, busyPortsList, attempt+1) // Обновлено с 60 на 180
			}
			time.Sleep(1 * time.Second)
		}
		if !allPortsFree {
			log.Printf("  ⚠ Порты не освободились за 180 секунд!") // Обновлено с 60 на 180
			log.Printf("  → Проверьте вручную: sudo lsof -i :%d", portsToFree[0])
			return
		}
	}
	time.Sleep(2 * time.Second)

	log.Printf("  → Запускаем новый контейнер...")
	cmd = exec.Command("docker-compose", "-f", composeFile, "up", "-d")
	cmd.Dir = containerDir
	output, err = cmd.CombinedOutput()
	log.Printf("  → Вывод docker-compose: %s", string(output))
	if err != nil && strings.Contains(string(output), "address already in use") {
		log.Printf("  ⚠ Порт всё ещё занят после ожидания освобождения!")
		log.Printf("  → Проверьте: docker ps -a | grep %s", clientTitle)
		log.Printf("  → Найдите процесс: sudo lsof -i | grep LISTEN | grep 127.0.0.1")
		return
	}
	time.Sleep(5 * time.Second)
	_, isRunning := checkContainerStatus(containerFullName)
	if !isRunning {
		log.Printf("  ⚠ Контейнер %s не запустился. Попробуйте вручную: cd %s && docker-compose up -d", containerFullName, containerDir)
		return
	}
	log.Printf("  ✓ Контейнер %s пересоздан с новыми портами", containerFullName)
}

func startContainer(containerFullName, composeFile, containerDir string) {
	log.Printf("  Контейнер %s остановлен, запускаем...", containerFullName)
	cmd := exec.Command("docker-compose", "-f", composeFile, "up", "-d")
	cmd.Dir = containerDir
	output, _ := cmd.CombinedOutput()
	log.Printf("  → Вывод docker-compose: %s", string(output))
	time.Sleep(5 * time.Second)
	_, isRunning := checkContainerStatus(containerFullName)
	if !isRunning {
		log.Printf("  ⚠ Контейнер %s не запустился. Попробуйте вручную: cd %s && docker-compose up -d", containerFullName, containerDir)
		return
	}
	log.Printf("  ✓ Контейнер %s запущен", containerFullName)
}

func checkContainerStatus(containerName string) (exists bool, running bool) {
	cmd := exec.Command("docker", "ps", "-a", "--filter", fmt.Sprintf("name=^%s$", containerName), "--format", "{{.Names}}")
	output, err := cmd.Output()
	if err != nil {
		return false, false
	}
	if strings.TrimSpace(string(output)) == "" {
		return false, false
	}
	cmd = exec.Command("docker", "ps", "--filter", fmt.Sprintf("name=^%s$", containerName), "--format", "{{.Names}}")
	output, err = cmd.Output()
	if err != nil {
		return true, false
	}
	running = strings.TrimSpace(string(output)) != ""
	return true, running
}

func forceRemoveContainer(containerName string) error {
	cmd := exec.Command("docker", "stop", "-t", "10", containerName)
	output, err := cmd.CombinedOutput()
	if err != nil {
		log.Printf("  ⚠ Ошибка остановки контейнера: %v, output: %s", err, string(output))
	}
	cmd = exec.Command("docker", "rm", "-f", containerName)
	output, err = cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("ошибка удаления контейнера: %w, output: %s", err, string(output))
	}
	return nil
}

func testNginxConfig(containerName string) error {
	log.Printf("    Проверка конфигурации nginx в контейнере %s", containerName)
	cmd := exec.Command("docker", "exec", containerName, "/opt/ptaf/bin/ptaf-nginx", "-t", "-c", "/opt/ptaf/conf/nginx.conf")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("конфигурация невалидна: %w, output: %s", err, string(output))
	}
	log.Printf("    Конфигурация валидна: %s", strings.TrimSpace(string(output)))
	return nil
}

func reloadNginx(containerName string) error {
	log.Printf("    Перезагрузка nginx в контейнере %s", containerName)
	cmd := exec.Command("docker", "exec", containerName, "/opt/ptaf/bin/ptaf-nginx", "-s", "reload")
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("ошибка перезагрузки: %w, output: %s", err, string(output))
	}
	log.Printf("    Nginx перезагружен успешно")
	return nil
}

func markContainerForDrain(containerName string, isMigration bool) error {
	timestamp := time.Now().Unix()
	markerFile := fmt.Sprintf("/tmp/ptaf-drain-%s", containerName)
	content := fmt.Sprintf("%d,%t", timestamp, isMigration)
	return os.WriteFile(markerFile, []byte(content), 0644)
}

func getContainerDrainInfo(containerName string) (timestamp int64, isMigration bool, found bool) {
	markerFile := fmt.Sprintf("/tmp/ptaf-drain-%s", containerName)
	data, err := os.ReadFile(markerFile)
	if err != nil {
		return 0, false, false
	}
	content := strings.TrimSpace(string(data))
	parts := strings.Split(content, ",")
	if len(parts) < 1 {
		return 0, false, false
	}
	ts, err := strconv.ParseInt(parts[0], 10, 64)
	if err != nil {
		return 0, false, false
	}
	migration := false
	if len(parts) >= 2 {
		migration = parts[1] == "true"
	}
	return ts, migration, true
}

func markAngieConfigForDrain(clientTitle string) error {
	markerFile := fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientTitle)
	return os.WriteFile(markerFile, []byte(fmt.Sprintf("%d", time.Now().Unix())), 0644)
}

func getAngieConfigDrainTimestamp(clientTitle string) (int64, bool) {
	markerFile := fmt.Sprintf("/tmp/ptaf-angie-drain-%s", clientTitle)
	data, err := os.ReadFile(markerFile)
	if err != nil {
		return 0, false
	}
	timestamp, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
	if err != nil {
		return 0, false
	}
	return timestamp, true
}

func stopAndRemoveContainer(containerName, composeFile, containerDir string) error {
	log.Printf("  Остановка контейнера %s", containerName)
	cmd := exec.Command("docker-compose", "-f", composeFile, "down", "-v")
	cmd.Dir = containerDir
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("ошибка остановки контейнера: %w, output: %s", err, string(output))
	}
	os.Remove(fmt.Sprintf("/tmp/ptaf-drain-%s", containerName))
	log.Printf("  ✓ Контейнер %s остановлен и удалён", containerName)
	return nil
}

func removeContainerFiles(clientTitle string, containerNum int) error {
	baseDir := filepath.Join("/home/install/ptaf", clientTitle)
	containerName := fmt.Sprintf("%s-ptaf-agent%03d", clientTitle, containerNum)
	for _, dir := range []string{
		filepath.Join(baseDir, containerName),
		filepath.Join("/home/install/conf/ptaf-nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum)),
		filepath.Join("/var/log/ptaf_nginx", clientTitle, fmt.Sprintf("ptaf-agent%03d", containerNum)),
	} {
		if _, err := os.Stat(dir); err == nil {
			log.Printf("  Удаление директории: %s", dir)
			if err := os.RemoveAll(dir); err != nil {
				log.Printf("  ⚠ Ошибка удаления %s: %v", dir, err)
			} else {
				log.Printf("  ✓ Удалена директория: %s", dir)
			}
		}
	}
	return nil
}

func cleanupClientDirectoryIfEmpty(clientTitle string) error {
	clientConfDir := fmt.Sprintf("/home/install/conf/ptaf-nginx/%s", clientTitle)
	if _, err := os.Stat(clientConfDir); os.IsNotExist(err) {
		return nil
	}
	entries, err := os.ReadDir(clientConfDir)
	if err != nil {
		return fmt.Errorf("ошибка чтения директории %s: %w", clientConfDir, err)
	}
	hasContainers := false
	for _, entry := range entries {
		if entry.IsDir() && strings.HasPrefix(entry.Name(), "ptaf-agent") {
			hasContainers = true
			break
		}
	}
	if !hasContainers {
		log.Printf("  ✓ У клиента %s не осталось контейнеров, удаляем директории клиента", clientTitle)
		os.RemoveAll(clientConfDir)
		ptafDir := fmt.Sprintf("/home/install/ptaf/%s", clientTitle)
		if _, err := os.Stat(ptafDir); err == nil {
			os.RemoveAll(ptafDir)
		}
		hashFile := fmt.Sprintf("/etc/angie/http.d/angie-ptaf-%s.conf.hash", clientTitle)
		if _, err := os.Stat(hashFile); err == nil {
			os.Remove(hashFile)
		}
	}
	return nil
}

func reloadAngie() {
	log.Printf("  Перезагрузка Angie...")
	cmd := exec.Command("systemctl", "reload", "angie")
	if err := cmd.Run(); err != nil {
		log.Printf("  ⚠ Ошибка перезагрузки Angie: %v", err)
	} else {
		log.Printf("  ✓ Angie перезагружен")
	}
}

// generateNginxConf генерирует основной nginx.conf для PTAF
func generateNginxConf(filePath string, config Config, ptafConfig string, existingPodIP string) (bool, error) {
	var randomIP string
	if existingPodIP != "" {
		randomIP = existingPodIP
		log.Printf("    → Используется существующий POD_IP: %s", randomIP)
	} else {
		randomIP = fmt.Sprintf("135.%d.%d.%d",
			rand.Intn(254)+1,
			rand.Intn(254)+1,
			rand.Intn(254)+1)
		log.Printf("    → Сгенерирован новый POD_IP: %s", randomIP)
	}

	pathParts := strings.Split(filePath, "/")
	clientTitle := ""
	containerNum := "001"
	for i, part := range pathParts {
		if part == "ptaf-nginx" && i+1 < len(pathParts) {
			clientTitle = pathParts[i+1]
		}
		if strings.HasPrefix(part, "ptaf-agent") {
			containerNum = strings.TrimPrefix(part, "ptaf-agent")
		}
	}

	hostname := fmt.Sprintf("ptaf-%s-agent%s", clientTitle, containerNum)
	log.Printf("    → clientTitle=%s, containerNum=%s, hostname=%s", clientTitle, containerNum, hostname)

	content := fmt.Sprintf(`#user ptaf;
master_process on;
worker_processes %d;
worker_rlimit_nofile 1048576;
daemon off;
load_module /opt/ptaf/lib/ngx_wrapper.so;
env POD_IP=%s;
env PTAF_LOG_LEVEL=INFO;
env DEBUG=false;
env HOSTNAME=%s;
error_log stderr notice;
pid /opt/ptaf/logs/nginx.pid;

events {
    worker_connections %d;
    accept_mutex off;
}

http {
    include /opt/ptaf/conf/mime.types;
    default_type application/octet-stream;
    
    log_format waf escape=json
        '{'
        '"SID":$http_x_sid,'
        '"server_name":"$server_name",'
        '"app_name":"$host",'
        '"proxyed_to":"$upstream_addr",'
        '"upstream_status":"$upstream_status",'
        '"upstream_response_length":"$upstream_response_length",'
        '"upstream_response_time":"$upstream_response_time",'
        '"upstream_bytes_sent":"$upstream_bytes_sent",'
        '"server_port":$server_port,'
        '"server_addr":"$server_addr",'
        '"server_protocol":"$server_protocol",'
        '"remote_addr":"$remote_addr",'
        '"response_status_code":$status,'
        '"response_body_bytes_sent":$body_bytes_sent,'
        '"http_x_forwarded_for":"$http_x_forwarded_for",'
        '"uri":"$uri",'
        '"http_user_agent":"$http_user_agent",'
        '"query_string":"$query_string",'
        '"request_method":"$request_method",'
        '"request_time":$request_time,'
        '"@timestamp":"$time_iso8601"'
        '}';
    
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    
    log_format main_upstream '$remote_addr - $remote_user [$time_local] "$request" '
                             '$status $body_bytes_sent "$http_referer" '
                             '"$http_user_agent" "$http_x_forwarded_for" $upstream_response_time';
    
    access_log /var/log/nginx/%s_access.log waf;
    
    client_max_body_size 0;
    sendfile on;
    gzip on;
    server_names_hash_bucket_size 512;
    server_tokens off;
    ptaf_fallback 503;
    proxy_read_timeout 120s;
    proxy_send_timeout 120s;
    send_timeout 60s;
    keepalive_timeout 80s;
    keepalive_requests 1000;
    proxy_buffer_size 8k;
    proxy_buffers 8 8k;
    proxy_busy_buffers_size 16k;
    proxy_connect_timeout 60s;
    resolver_timeout 30s;
    
    geo $dollar {
        default "$";
    }
    
    map $http_upgrade $proxy_http_connection {
        default upgrade;
        '' '';
    }
    
    # Map для условного Connection header (используется в server блоках)
    map $http_upgrade $proxy_connection {
        default upgrade;
        '' '';
    }
    
    # Map для определения backend host из X-Original-Host заголовка
    map $http_x_original_host $backend_host {
        "" $host;
        default $http_x_original_host;
    }
    
    ptaf_config %s;
    
    include /opt/ptaf/conf/conf.d/*.conf;
}
`, config.WorkerProcesses, randomIP, hostname, config.WorkerConnections, clientTitle, ptafConfig)

	return writeFileIfChanged(filePath, content)
}

// generateMimeTypes генерирует файл mime.types
func generateMimeTypes(filePath string) (bool, error) {
	content := `types {
    text/html                             html htm shtml;
    text/css                              css;
    text/xml                              xml;
    image/gif                             gif;
    image/jpeg                            jpeg jpg;
    application/javascript                js;
    application/atom+xml                  atom;
    application/rss+xml                   rss;

    text/mathml                           mml;
    text/plain                            txt;
    text/vnd.sun.j2me.app-descriptor      jad;
    text/vnd.wap.wml                      wml;
    text/x-component                      htc;

    image/avif                            avif;
    image/png                             png;
    image/svg+xml                         svg svgz;
    image/tiff                            tif tiff;
    image/vnd.wap.wbmp                    wbmp;
    image/webp                            webp;
    image/x-icon                          ico;
    image/x-jng                           jng;
    image/x-ms-bmp                        bmp;

    font/woff                             woff;
    font/woff2                            woff2;

    application/java-archive              jar war ear;
    application/json                      json;
    application/mac-binhex40              hqx;
    application/msword                    doc;
    application/pdf                       pdf;
    application/postscript                ps eps ai;
    application/rtf                       rtf;
    application/vnd.apple.mpegurl         m3u8;
    application/vnd.google-earth.kml+xml  kml;
    application/vnd.google-earth.kmz      kmz;
    application/vnd.ms-excel              xls;
    application/vnd.ms-fontobject         eot;
    application/vnd.ms-powerpoint         ppt;
    application/vnd.oasis.opendocument.graphics        odg;
    application/vnd.oasis.opendocument.presentation    odp;
    application/vnd.oasis.opendocument.spreadsheet     ods;
    application/vnd.oasis.opendocument.text            odt;
    application/vnd.openxmlformats-officedocument.presentationml.presentation    pptx;
    application/vnd.openxmlformats-officedocument.spreadsheetml.sheet          xlsx;
    application/vnd.openxmlformats-officedocument.wordprocessingml.document    docx;
    application/vnd.wap.wmlc              wmlc;
    application/wasm                      wasm;
    application/x-7z-compressed           7z;
    application/x-cocoa                   cco;
    application/x-java-archive-diff       jardiff;
    application/x-java-jnlp-file          jnlp;
    application/x-makeself                run;
    application/x-perl                    pl pm;
    application/x-pilot                   prc pdb;
    application/x-rar-compressed          rar;
    application/x-redhat-package-manager  rpm;
    application/x-sea                     sea;
    application/x-shockwave-flash         swf;
    application/x-stuffit                 sit;
    application/x-tcl                     tcl tk;
    application/x-x509-ca-cert            der pem crt;
    application/x-xpinstall               xpi;
    application/xhtml+xml                 xhtml;
    application/xspf+xml                  xspf;
    application/zip                       zip;

    application/octet-stream              bin exe dll;
    application/octet-stream              deb;
    application/octet-stream              dmg;
    application/octet-stream              iso img;
    application/octet-stream              msi msp msm;

    audio/midi                            mid midi kar;
    audio/mpeg                            mp3;
    audio/ogg                             ogg;
    audio/x-m4a                           m4a;
    audio/x-realaudio                     ra;

    video/3gpp                            3gpp 3gp;
    video/mp2t                            ts;
    video/mp4                             mp4;
    video/mpeg                            mpeg mpg;
    video/quicktime                       mov;
    video/webm                            webm;
    video/x-flv                           flv;
    video/x-m4v                           m4v;
    video/x-mng                           mng;
    video/x-ms-asf                        asx asf;
    video/x-ms-wmv                        wmv;
    video/x-msvideo                       avi;
}
`
	return writeFileIfChanged(filePath, content)
}

// generateDockerCompose генерирует docker-compose.yml файл
func generateDockerCompose(filePath string, config Config, clientTitle string, clientInfo *ClientInfo, containerNum int, resources []ResourceData, portMappings []PortMapping) (bool, error) {
	type ComposeData struct {
		ClientTitle       string
		ContainerNum      int
		DockerImage       string
		PTAFConfig        string
		WorkerProcesses   int
		WorkerConnections int
		FluentBitEnabled  bool
		FluentBitPort     int
		HasMTLS           bool
		SortedHTTPPorts   []SortedPortMapping
		SortedHTTPSPorts  []SortedPortMapping
	}

	hasMTLS := false
	for _, res := range resources {
		if res.AppsSettings.LocationNginxCustom.Valid && hasMTLSCertificates(res.AppsSettings.LocationNginxCustom.String) {
			hasMTLS = true
			break
		}
		if res.AppsSettings.ServerNginxCustom.Valid && hasMTLSCertificates(res.AppsSettings.ServerNginxCustom.String) {
			hasMTLS = true
			break
		}
	}

	var allHTTPPorts []SortedPortMapping
	var allHTTPSPorts []SortedPortMapping

	for _, pm := range portMappings {
		var httpKeys []int
		for customPort := range pm.HTTPPorts {
			httpKeys = append(httpKeys, customPort)
		}
		sort.Ints(httpKeys)
		for _, customPort := range httpKeys {
			allHTTPPorts = append(allHTTPPorts, SortedPortMapping{CustomPort: customPort, DockerPort: pm.HTTPPorts[customPort]})
		}

		var httpsKeys []int
		for customPort := range pm.HTTPSPorts {
			httpsKeys = append(httpsKeys, customPort)
		}
		sort.Ints(httpsKeys)
		for _, customPort := range httpsKeys {
			allHTTPSPorts = append(allHTTPSPorts, SortedPortMapping{CustomPort: customPort, DockerPort: pm.HTTPSPorts[customPort]})
		}
	}

	sort.Slice(allHTTPPorts, func(i, j int) bool { return allHTTPPorts[i].DockerPort < allHTTPPorts[j].DockerPort })
	sort.Slice(allHTTPSPorts, func(i, j int) bool { return allHTTPSPorts[i].DockerPort < allHTTPSPorts[j].DockerPort })

	data := ComposeData{
		ClientTitle:       clientTitle,
		ContainerNum:      containerNum,
		DockerImage:       config.DockerImage,
		PTAFConfig:        "",
		WorkerProcesses:   config.WorkerProcesses,
		WorkerConnections: config.WorkerConnections,
		FluentBitEnabled:  false,
		HasMTLS:           hasMTLS,
		SortedHTTPPorts:   allHTTPPorts,
		SortedHTTPSPorts:  allHTTPSPorts,
	}

	if clientInfo.PTAFConfig.Valid {
		data.PTAFConfig = clientInfo.PTAFConfig.String
	}
	if clientInfo.FluentBitPort.Valid {
		data.FluentBitEnabled = true
		data.FluentBitPort = int(clientInfo.FluentBitPort.Int64)
	}

	tmpl := `services:
  ptaf-{{.ClientTitle}}-agent{{printf "%03d" .ContainerNum}}:
    image: '{{.DockerImage}}'
    restart: unless-stopped
    shm_size: '1gb'
    container_name: ptaf_{{.ClientTitle}}_{{printf "%03d" .ContainerNum}}
    hostname: ptaf_{{.ClientTitle}}_{{printf "%03d" .ContainerNum}}
    environment:
      - CONNECTION_STRING={{.PTAFConfig}}
      - WORKER_PROCESSES={{.WorkerProcesses}}
      - WORKER_CONNECTIONS={{.WorkerConnections}}
      - INCLUDE_CUSTOM_CONFIG=True
      - PTAF_CONFIGURATOR_DISABLED=1
{{- if .FluentBitEnabled}}
      - EVENT_SENDER_ENABLED=1
      - EVENT_SENDER_HOST=172.17.0.1
      - EVENT_SENDER_PORT={{.FluentBitPort}}
      - EVENT_SENDER_TIMEOUT=3
      - EVENT_WEB_UI_URL=https://af.ptcloud.ru/events/attacks/%s/general/
{{- end}}
    sysctls:
      kernel.sem: "32000 1024000000 500 32000"
    ports:
{{- range .SortedHTTPPorts}}
      - 127.0.0.1:{{.DockerPort}}:{{.CustomPort}}/tcp
{{- end}}
{{- range .SortedHTTPSPorts}}
      - 127.0.0.1:{{.DockerPort}}:{{.CustomPort}}/tcp
{{- end}}
    volumes:
      - /var/log/ptaf_nginx/{{.ClientTitle}}/ptaf-agent{{printf "%03d" .ContainerNum}}:/var/log/nginx
      - /home/install/conf/ptaf-nginx/{{.ClientTitle}}/ptaf-agent{{printf "%03d" .ContainerNum}}/conf.d:/opt/ptaf/conf/conf.d
      - /home/install/conf/ptaf-nginx/{{.ClientTitle}}/ptaf-agent{{printf "%03d" .ContainerNum}}/nginx.conf:/opt/ptaf/conf/nginx.conf
      - /home/install/conf/ptaf-nginx/{{.ClientTitle}}/ptaf-agent{{printf "%03d" .ContainerNum}}/mime.types:/opt/ptaf/conf/mime.types
{{- if .HasMTLS}}
      - /etc/ssl/mtls:/opt/ptaf/conf/main
{{- end}}
    networks:
      - ptaf-{{.ClientTitle}}-net
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "5"
networks:
  ptaf-{{.ClientTitle}}-net:
    driver: bridge
`

	t, err := template.New("compose").Parse(tmpl)
	if err != nil {
		return false, fmt.Errorf("ошибка парсинга шаблона: %w", err)
	}

	var buf strings.Builder
	if err = t.Execute(&buf, data); err != nil {
		return false, fmt.Errorf("ошибка генерации файла: %w", err)
	}

	return writeFileIfChanged(filePath, buf.String())
}
