package infra

import (
	"database/sql"
	"os"
	"path/filepath"
	"testing"

	_ "github.com/mattn/go-sqlite3"
)

const wgRevokeTestConfig = `[Interface]
Address = 10.0.0.1/24
PrivateKey = SERVERKEY==

# DeviceID:device-revoke-001
[Peer]
PublicKey = PUBKEYREVOKE001==
AllowedIPs = 10.0.0.10/32

# DeviceID:device-revoke-002
[Peer]
PublicKey = PUBKEYREVOKE002==
AllowedIPs = 10.0.0.11/32
`

func TestWGPeerRevoke(t *testing.T) {
	origSyncConf := wgSyncConfFn
	wgSyncConfFn = func(iface, configPath string) error { return nil }
	defer func() { wgSyncConfFn = origSyncConf }()

	origBlacklist := wgAppendBlacklistFn
	wgAppendBlacklistFn = func(line string) error { return nil }
	defer func() { wgAppendBlacklistFn = origBlacklist }()

	t.Run("audit DB contiene registro con this_hash != prev_hash", func(t *testing.T) {
		dir := t.TempDir()
		configPath := filepath.Join(dir, "wg0.conf")
		auditDBPath := filepath.Join(dir, "revoked.db")

		if err := os.WriteFile(configPath, []byte(wgRevokeTestConfig), 0600); err != nil {
			t.Fatalf("write config: %v", err)
		}

		audit, err := WGPeerRevoke("device-revoke-001", "operator-alice", "dispositivo perdido", configPath, auditDBPath)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}

		if audit.ThisHash == "" {
			t.Error("this_hash is empty")
		}
		// En el primer registro prev_hash es vacio — this_hash debe diferir siempre.
		if audit.ThisHash == audit.PrevHash {
			t.Errorf("this_hash == prev_hash (%q), expected different values", audit.ThisHash)
		}
		if audit.PublicKey != "PUBKEYREVOKE001==" {
			t.Errorf("public_key=%q, want PUBKEYREVOKE001==", audit.PublicKey)
		}

		// Verificar en la BD directamente.
		db, err := sql.Open("sqlite3", auditDBPath)
		if err != nil {
			t.Fatalf("open audit db: %v", err)
		}
		defer db.Close()

		var storedHash, storedPubKey string
		if err := db.QueryRow("SELECT this_hash, public_key FROM revoked_peers WHERE device_id = ?",
			"device-revoke-001").Scan(&storedHash, &storedPubKey); err != nil {
			t.Fatalf("query audit record: %v", err)
		}
		if storedHash != audit.ThisHash {
			t.Errorf("stored hash=%q, want %q", storedHash, audit.ThisHash)
		}
		if storedPubKey != "PUBKEYREVOKE001==" {
			t.Errorf("stored public_key=%q, want PUBKEYREVOKE001==", storedPubKey)
		}
	})

	t.Run("segunda revoke del mismo peer → error already revoked", func(t *testing.T) {
		dir := t.TempDir()
		configPath := filepath.Join(dir, "wg0.conf")
		auditDBPath := filepath.Join(dir, "revoked.db")

		if err := os.WriteFile(configPath, []byte(wgRevokeTestConfig), 0600); err != nil {
			t.Fatalf("write config: %v", err)
		}

		// Primera revocacion.
		if _, err := WGPeerRevoke("device-revoke-002", "operator-bob", "comprometido", configPath, auditDBPath); err != nil {
			t.Fatalf("first revoke unexpected error: %v", err)
		}

		// Segunda revocacion del mismo deviceID → debe fallar por audit DB, no por config.
		_, err := WGPeerRevoke("device-revoke-002", "operator-bob", "segundo intento", configPath, auditDBPath)
		if err == nil {
			t.Fatal("expected error on second revoke, got nil")
		}
		if !contains(err.Error(), "already revoked") {
			t.Errorf("expected 'already revoked' error, got: %v", err)
		}
	})
}
