package bot

import (
	"testing"
	"time"

	"github.com/salvacybersec/keyhunter/pkg/engine"
	"github.com/salvacybersec/keyhunter/pkg/scheduler"
	"github.com/salvacybersec/keyhunter/pkg/storage"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func openTestDB(t *testing.T) *storage.DB {
	t.Helper()
	db, err := storage.Open(":memory:")
	require.NoError(t, err)
	t.Cleanup(func() { _ = db.Close() })
	return db
}

func TestSubscribeUnsubscribe(t *testing.T) {
	db := openTestDB(t)

	// Initially not subscribed.
	ok, err := db.IsSubscribed(12345)
	require.NoError(t, err)
	assert.False(t, ok, "should not be subscribed initially")

	// Subscribe.
	err = db.AddSubscriber(12345, "testuser")
	require.NoError(t, err)

	ok, err = db.IsSubscribed(12345)
	require.NoError(t, err)
	assert.True(t, ok, "should be subscribed after AddSubscriber")

	// Unsubscribe.
	rows, err := db.RemoveSubscriber(12345)
	require.NoError(t, err)
	assert.Equal(t, int64(1), rows, "should have removed 1 row")

	ok, err = db.IsSubscribed(12345)
	require.NoError(t, err)
	assert.False(t, ok, "should not be subscribed after RemoveSubscriber")

	// Unsubscribe again returns 0 rows.
	rows, err = db.RemoveSubscriber(12345)
	require.NoError(t, err)
	assert.Equal(t, int64(0), rows, "should have removed 0 rows when not subscribed")
}

func TestNotifyNewFindings_NoSubscribers(t *testing.T) {
	db := openTestDB(t)

	b := &Bot{cfg: Config{DB: db}}
	sent, errs := b.NotifyNewFindings(scheduler.JobResult{
		JobName:      "nightly-scan",
		FindingCount: 5,
		Duration:     10 * time.Second,
	})
	assert.Equal(t, 0, sent, "should send 0 messages with no subscribers")
	assert.Empty(t, errs, "should have no errors with no subscribers")
}

func TestNotifyNewFindings_ZeroFindings(t *testing.T) {
	db := openTestDB(t)
	_ = db.AddSubscriber(12345, "user1")

	b := &Bot{cfg: Config{DB: db}}
	sent, errs := b.NotifyNewFindings(scheduler.JobResult{
		JobName:      "nightly-scan",
		FindingCount: 0,
		Duration:     3 * time.Second,
	})
	assert.Equal(t, 0, sent, "should not notify for zero findings")
	assert.Empty(t, errs, "should have no errors for zero findings")
}

func TestFormatNotification(t *testing.T) {
	result := scheduler.JobResult{
		JobName:      "nightly-scan",
		FindingCount: 7,
		Duration:     2*time.Minute + 30*time.Second,
	}
	msg := formatNotification(result)
	assert.Contains(t, msg, "nightly-scan", "message should contain job name")
	assert.Contains(t, msg, "7", "message should contain finding count")
	assert.Contains(t, msg, "2m30s", "message should contain duration")
	assert.Contains(t, msg, "/stats", "message should reference /stats command")
}

func TestFormatNotification_Error(t *testing.T) {
	result := scheduler.JobResult{
		JobName:      "daily-scan",
		FindingCount: 0,
		Duration:     5 * time.Second,
		Error:        assert.AnError,
	}
	msg := formatErrorNotification(result)
	assert.Contains(t, msg, "daily-scan", "error message should contain job name")
	assert.Contains(t, msg, "error", "error message should indicate error")
}

func TestFormatFindingNotification(t *testing.T) {
	finding := engine.Finding{
		ProviderName: "OpenAI",
		KeyValue:     "sk-proj-1234567890abcdef",
		KeyMasked:    "sk-proj-...cdef",
		Confidence:   "high",
		Source:       "/tmp/test.py",
		LineNumber:   42,
	}
	msg := formatFindingNotification(finding)
	assert.Contains(t, msg, "OpenAI", "should contain provider name")
	assert.Contains(t, msg, "sk-proj-...cdef", "should contain masked key")
	assert.NotContains(t, msg, "sk-proj-1234567890abcdef", "should NOT contain full key")
	assert.Contains(t, msg, "/tmp/test.py", "should contain source path")
	assert.Contains(t, msg, "42", "should contain line number")
	assert.Contains(t, msg, "high", "should contain confidence")
}
