package audit

import (
	"encoding/json"
	"log/slog"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/enmanuel/agents/internal/config"
)

func testLogger() *slog.Logger {
	return slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError}))
}

func TestEmit_WritesToFile(t *testing.T) {
	dir := t.TempDir()
	logFile := filepath.Join(dir, "audit.jsonl")

	cfg := config.AuditCfg{
		Enabled: true,
		LogFile: logFile,
	}

	w, err := New(cfg, nil, testLogger())
	if err != nil {
		t.Fatalf("New: %v", err)
	}
	defer w.Close()

	w.Emit(Event{
		Time:      time.Date(2026, 4, 9, 12, 0, 0, 0, time.UTC),
		AgentID:   "test-bot",
		EventType: EventCommandExec,
		SenderID:  "@user:example.com",
		RoomID:    "!room:example.com",
		Detail:    "command=help",
	})

	data, err := os.ReadFile(logFile)
	if err != nil {
		t.Fatalf("ReadFile: %v", err)
	}

	lines := strings.Split(strings.TrimSpace(string(data)), "\n")
	if len(lines) != 1 {
		t.Fatalf("expected 1 line, got %d", len(lines))
	}

	var evt Event
	if err := json.Unmarshal([]byte(lines[0]), &evt); err != nil {
		t.Fatalf("Unmarshal: %v", err)
	}

	if evt.AgentID != "test-bot" {
		t.Errorf("AgentID = %q, want %q", evt.AgentID, "test-bot")
	}
	if evt.EventType != EventCommandExec {
		t.Errorf("EventType = %q, want %q", evt.EventType, EventCommandExec)
	}
	if evt.SenderID != "@user:example.com" {
		t.Errorf("SenderID = %q, want %q", evt.SenderID, "@user:example.com")
	}
	if evt.Detail != "command=help" {
		t.Errorf("Detail = %q, want %q", evt.Detail, "command=help")
	}
}

func TestEmit_IncludeFilter(t *testing.T) {
	dir := t.TempDir()
	logFile := filepath.Join(dir, "audit.jsonl")

	cfg := config.AuditCfg{
		Enabled: true,
		LogFile: logFile,
		Include: []string{EventCommandExec, EventToolExec},
	}

	w, err := New(cfg, nil, testLogger())
	if err != nil {
		t.Fatalf("New: %v", err)
	}
	defer w.Close()

	// Should be written (in include list)
	w.Emit(Event{AgentID: "bot", EventType: EventCommandExec, Detail: "included"})

	// Should NOT be written (not in include list)
	w.Emit(Event{AgentID: "bot", EventType: EventLLMRequest, Detail: "excluded"})

	// Should be written
	w.Emit(Event{AgentID: "bot", EventType: EventToolExec, Detail: "also-included"})

	data, err := os.ReadFile(logFile)
	if err != nil {
		t.Fatalf("ReadFile: %v", err)
	}

	lines := strings.Split(strings.TrimSpace(string(data)), "\n")
	if len(lines) != 2 {
		t.Fatalf("expected 2 lines (filtered), got %d: %s", len(lines), string(data))
	}

	// Verify content
	var evt0, evt1 Event
	json.Unmarshal([]byte(lines[0]), &evt0)
	json.Unmarshal([]byte(lines[1]), &evt1)

	if evt0.EventType != EventCommandExec {
		t.Errorf("line 0 EventType = %q, want %q", evt0.EventType, EventCommandExec)
	}
	if evt1.EventType != EventToolExec {
		t.Errorf("line 1 EventType = %q, want %q", evt1.EventType, EventToolExec)
	}
}

func TestEmit_NoLogFile_OnlyRoom(t *testing.T) {
	var sent []string
	var mu sync.Mutex
	sender := func(roomID, msg string) {
		mu.Lock()
		sent = append(sent, roomID+"|"+msg)
		mu.Unlock()
	}

	cfg := config.AuditCfg{
		Enabled:   true,
		LogToRoom: "!audit:example.com",
		// No LogFile
	}

	w, err := New(cfg, sender, testLogger())
	if err != nil {
		t.Fatalf("New: %v", err)
	}
	defer w.Close()

	w.Emit(Event{AgentID: "bot", EventType: EventMessageReceived, Detail: "test"})

	mu.Lock()
	defer mu.Unlock()
	if len(sent) != 1 {
		t.Fatalf("expected 1 message sent, got %d", len(sent))
	}
	if !strings.HasPrefix(sent[0], "!audit:example.com|") {
		t.Errorf("message sent to wrong room: %s", sent[0])
	}
}

func TestEmit_NoRoom_OnlyFile(t *testing.T) {
	dir := t.TempDir()
	logFile := filepath.Join(dir, "audit.jsonl")

	senderCalled := false
	sender := func(roomID, msg string) {
		senderCalled = true
	}

	cfg := config.AuditCfg{
		Enabled: true,
		LogFile: logFile,
		// No LogToRoom
	}

	w, err := New(cfg, sender, testLogger())
	if err != nil {
		t.Fatalf("New: %v", err)
	}
	defer w.Close()

	w.Emit(Event{AgentID: "bot", EventType: EventCommandExec, Detail: "test"})

	if senderCalled {
		t.Error("sender was called despite no LogToRoom configured")
	}

	data, err := os.ReadFile(logFile)
	if err != nil {
		t.Fatalf("ReadFile: %v", err)
	}
	if len(strings.TrimSpace(string(data))) == 0 {
		t.Error("expected data in log file, got empty")
	}
}

func TestEmit_SetsTimeIfZero(t *testing.T) {
	dir := t.TempDir()
	logFile := filepath.Join(dir, "audit.jsonl")

	cfg := config.AuditCfg{
		Enabled: true,
		LogFile: logFile,
	}

	w, err := New(cfg, nil, testLogger())
	if err != nil {
		t.Fatalf("New: %v", err)
	}
	defer w.Close()

	before := time.Now().UTC()
	w.Emit(Event{AgentID: "bot", EventType: EventCommandExec})
	after := time.Now().UTC()

	data, err := os.ReadFile(logFile)
	if err != nil {
		t.Fatalf("ReadFile: %v", err)
	}

	var evt Event
	json.Unmarshal([]byte(strings.TrimSpace(string(data))), &evt)

	if evt.Time.Before(before) || evt.Time.After(after) {
		t.Errorf("Time %v not in range [%v, %v]", evt.Time, before, after)
	}
}

func TestEmit_EmptyInclude_PassesAll(t *testing.T) {
	dir := t.TempDir()
	logFile := filepath.Join(dir, "audit.jsonl")

	cfg := config.AuditCfg{
		Enabled: true,
		LogFile: logFile,
		Include: []string{}, // empty = pass all
	}

	w, err := New(cfg, nil, testLogger())
	if err != nil {
		t.Fatalf("New: %v", err)
	}
	defer w.Close()

	w.Emit(Event{AgentID: "bot", EventType: EventCommandExec})
	w.Emit(Event{AgentID: "bot", EventType: EventLLMRequest})
	w.Emit(Event{AgentID: "bot", EventType: EventToolExec})

	data, err := os.ReadFile(logFile)
	if err != nil {
		t.Fatalf("ReadFile: %v", err)
	}

	lines := strings.Split(strings.TrimSpace(string(data)), "\n")
	if len(lines) != 3 {
		t.Errorf("expected 3 lines (all passed), got %d", len(lines))
	}
}

func TestNew_CreatesDirectory(t *testing.T) {
	dir := t.TempDir()
	logFile := filepath.Join(dir, "subdir", "nested", "audit.jsonl")

	cfg := config.AuditCfg{
		Enabled: true,
		LogFile: logFile,
	}

	w, err := New(cfg, nil, testLogger())
	if err != nil {
		t.Fatalf("New: %v", err)
	}
	w.Close()

	if _, err := os.Stat(logFile); os.IsNotExist(err) {
		t.Error("log file was not created")
	}
}
