package infra

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
)

// WGPeerRemoveStatus indica el resultado de la operacion de borrado.
type WGPeerRemoveStatus string

const (
	WGPeerRemoveStatusRemoved    WGPeerRemoveStatus = "removed"
	WGPeerRemoveStatusNotPresent WGPeerRemoveStatus = "not-present"
)

// WGPeerRemoveResult contiene el resultado de WGPeerRemove.
type WGPeerRemoveResult struct {
	DeviceID   string
	Status     WGPeerRemoveStatus
	ConfigPath string
}

// WGPeerRemove elimina el bloque [Peer] asociado a deviceID del archivo configPath
// buscando el comentario "# DeviceID:<deviceID>" y aplica syncconf en caliente.
// Es idempotente: si el peer no existe devuelve status=not-present sin error.
//
// Formato esperado del config (el comentario puede preceder o estar dentro del bloque):
//
//	# DeviceID:device-001
//	[Peer]
//	PublicKey = ...
//	AllowedIPs = ...
func WGPeerRemove(deviceID string, configPath string) (WGPeerRemoveResult, error) {
	result := WGPeerRemoveResult{
		DeviceID:   deviceID,
		ConfigPath: configPath,
	}

	if strings.TrimSpace(deviceID) == "" {
		return result, fmt.Errorf("wg_peer_remove: deviceID cannot be empty")
	}
	if strings.TrimSpace(configPath) == "" {
		return result, fmt.Errorf("wg_peer_remove: configPath cannot be empty")
	}

	data, err := os.ReadFile(configPath)
	if err != nil {
		return result, fmt.Errorf("wg_peer_remove: read config %s: %w", configPath, err)
	}

	lines := strings.Split(string(data), "\n")
	marker := fmt.Sprintf("# DeviceID:%s", deviceID)

	// Localizar el marker.
	markerIdx := -1
	for i, line := range lines {
		if strings.TrimSpace(line) == marker {
			markerIdx = i
			break
		}
	}
	if markerIdx == -1 {
		result.Status = WGPeerRemoveStatusNotPresent
		return result, nil
	}

	// blockStart: la primera linea del bloque a eliminar.
	// Si el marker precede al [Peer], el bloque empieza en el marker.
	// Si el marker esta dentro del [Peer], retrocedemos hasta el [Peer] o su comentario previo.
	blockStart := markerIdx

	for j := markerIdx - 1; j >= 0; j-- {
		t := strings.TrimSpace(lines[j])
		if t == "[Peer]" {
			blockStart = j
			// Incluir tambien el comentario # DeviceID que preceda a ese [Peer].
			if j > 0 && strings.HasPrefix(strings.TrimSpace(lines[j-1]), "# DeviceID:") {
				blockStart = j - 1
			}
			break
		}
		if strings.HasPrefix(t, "[") && t != "" {
			// Llegamos a otra seccion — el marker precede al [Peer].
			break
		}
	}

	// blockEnd: primera linea que abre el SIGUIENTE bloque tras el [Peer] actual.
	// Saltamos hasta pasar el [Peer] propio antes de buscar otra seccion.
	blockEnd := len(lines)
	pastOwnPeer := false
	for i := blockStart; i < len(lines); i++ {
		t := strings.TrimSpace(lines[i])
		if !pastOwnPeer {
			if t == "[Peer]" {
				pastOwnPeer = true
			}
			continue
		}
		if (strings.HasPrefix(t, "[") && t != "") || strings.HasPrefix(t, "# DeviceID:") {
			blockEnd = i
			break
		}
	}

	// Reconstruir: segmento antes del bloque + segmento desde blockEnd.
	before := lines[:blockStart]
	after := lines[blockEnd:]

	// Quitar lineas vacias finales del segmento anterior.
	for len(before) > 0 && strings.TrimSpace(before[len(before)-1]) == "" {
		before = before[:len(before)-1]
	}

	var newLines []string
	newLines = append(newLines, before...)
	if len(after) > 0 {
		newLines = append(newLines, "")
	}
	newLines = append(newLines, after...)

	newContent := strings.TrimRight(strings.Join(newLines, "\n"), "\n") + "\n"

	if err := os.WriteFile(configPath, []byte(newContent), 0600); err != nil {
		return result, fmt.Errorf("wg_peer_remove: write config %s: %w", configPath, err)
	}

	// Aplicar syncconf en caliente.
	iface := wgIfaceFromPath(configPath)
	if iface != "" {
		if err := wgSyncConfFn(iface, configPath); err != nil {
			_ = os.WriteFile(configPath, data, 0600)
			return result, fmt.Errorf("wg_peer_remove: syncconf %s: %w", iface, err)
		}
	}

	result.Status = WGPeerRemoveStatusRemoved
	return result, nil
}

// wgIfaceFromPath extrae el nombre de interfaz del path del config (ej. wg0 de /etc/wireguard/wg0.conf).
func wgIfaceFromPath(configPath string) string {
	parts := strings.Split(configPath, "/")
	if len(parts) == 0 {
		return ""
	}
	name := parts[len(parts)-1]
	name = strings.TrimSuffix(name, ".conf")
	for _, c := range name {
		if !((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '_') {
			return ""
		}
	}
	return name
}

// wgSyncConfFn aplica `wg syncconf <iface> <configPath>` en caliente. Variable
// para permitir override en tests (no requiere binario `wg`). WG_SKIP_SYNCCONF=1
// salta la ejecucion (CI sin wg-tools).
var wgSyncConfFn = func(iface, configPath string) error {
	if os.Getenv("WG_SKIP_SYNCCONF") == "1" {
		return nil
	}
	cmd := exec.Command("wg", "syncconf", iface, configPath)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("%w: %s", err, strings.TrimSpace(string(out)))
	}
	return nil
}

// wgLookupPeerPublicKey busca la PublicKey del peer identificado por deviceID en configPath.
// El comentario "# DeviceID:<id>" puede preceder al [Peer] o estar dentro del bloque.
func wgLookupPeerPublicKey(deviceID, configPath string) (string, error) {
	data, err := os.ReadFile(configPath)
	if err != nil {
		return "", fmt.Errorf("wg_lookup_peer: read %s: %w", configPath, err)
	}

	lines := strings.Split(string(data), "\n")
	marker := fmt.Sprintf("# DeviceID:%s", deviceID)

	markerIdx := -1
	for i, line := range lines {
		if strings.TrimSpace(line) == marker {
			markerIdx = i
			break
		}
	}
	if markerIdx == -1 {
		return "", fmt.Errorf("wg_lookup_peer: peer %s not found in %s", deviceID, configPath)
	}

	// Buscar PublicKey hacia adelante desde el marker (saltando la linea [Peer]).
	for i := markerIdx + 1; i < len(lines); i++ {
		line := strings.TrimSpace(lines[i])
		if line == "[Peer]" {
			continue
		}
		if (strings.HasPrefix(line, "[") && line != "") || strings.HasPrefix(line, "# DeviceID:") {
			break
		}
		if strings.HasPrefix(line, "PublicKey") {
			if idx := strings.Index(line, " = "); idx != -1 {
				return strings.TrimSpace(line[idx+3:]), nil
			}
			if idx := strings.Index(line, "="); idx != -1 {
				return strings.TrimSpace(line[idx+1:]), nil
			}
		}
	}

	// Fallback: buscar hacia atras (marker dentro del bloque).
	for i := markerIdx - 1; i >= 0; i-- {
		line := strings.TrimSpace(lines[i])
		if strings.HasPrefix(line, "[") && line != "" {
			break
		}
		if strings.HasPrefix(line, "PublicKey") {
			if idx := strings.Index(line, " = "); idx != -1 {
				return strings.TrimSpace(line[idx+3:]), nil
			}
			if idx := strings.Index(line, "="); idx != -1 {
				return strings.TrimSpace(line[idx+1:]), nil
			}
		}
	}

	return "", fmt.Errorf("wg_lookup_peer: PublicKey not found for peer %s in %s", deviceID, configPath)
}
