package output

import (
	"bytes"
	"encoding/csv"
	"strings"
	"testing"
	"time"

	"github.com/salvacybersec/keyhunter/pkg/engine"
)

func csvSampleFinding() engine.Finding {
	return engine.Finding{
		ProviderName: "anthropic",
		KeyValue:     "sk-ant-api03-FULLKEY1234567890",
		KeyMasked:    "sk-ant-a...7890",
		Confidence:   "medium",
		Source:       "src/client.go",
		SourceType:   "file",
		LineNumber:   17,
		DetectedAt:   time.Date(2026, 4, 5, 8, 15, 0, 0, time.UTC),
	}
}

func TestCSVFormatter_RegisteredUnderCSV(t *testing.T) {
	f, err := Get("csv")
	if err != nil {
		t.Fatalf("Get(\"csv\") error: %v", err)
	}
	if _, ok := f.(CSVFormatter); !ok {
		t.Fatalf("Get(\"csv\") returned %T, want CSVFormatter", f)
	}
}

func TestCSVFormatter_HeaderOnly(t *testing.T) {
	var buf bytes.Buffer
	if err := (CSVFormatter{}).Format(nil, &buf, Options{}); err != nil {
		t.Fatalf("Format error: %v", err)
	}
	r := csv.NewReader(strings.NewReader(buf.String()))
	rows, err := r.ReadAll()
	if err != nil {
		t.Fatalf("csv parse error: %v\n%s", err, buf.String())
	}
	if len(rows) != 1 {
		t.Fatalf("want 1 row (header only), got %d: %v", len(rows), rows)
	}
	wantHeader := []string{"id", "provider", "confidence", "key", "source", "line", "detected_at", "verified", "verify_status"}
	if len(rows[0]) != len(wantHeader) {
		t.Fatalf("header length: got %d want %d", len(rows[0]), len(wantHeader))
	}
	for i, col := range wantHeader {
		if rows[0][i] != col {
			t.Errorf("header[%d]: got %q want %q", i, rows[0][i], col)
		}
	}
}

func TestCSVFormatter_RowMasked(t *testing.T) {
	f := csvSampleFinding()
	var buf bytes.Buffer
	if err := (CSVFormatter{}).Format([]engine.Finding{f}, &buf, Options{}); err != nil {
		t.Fatalf("Format error: %v", err)
	}
	r := csv.NewReader(strings.NewReader(buf.String()))
	rows, err := r.ReadAll()
	if err != nil {
		t.Fatalf("csv parse error: %v", err)
	}
	if len(rows) != 2 {
		t.Fatalf("want 2 rows, got %d", len(rows))
	}
	row := rows[1]
	if row[0] != "0" {
		t.Errorf("id: got %q want 0", row[0])
	}
	if row[1] != "anthropic" {
		t.Errorf("provider: got %q", row[1])
	}
	if row[2] != "medium" {
		t.Errorf("confidence: got %q", row[2])
	}
	if row[3] != f.KeyMasked {
		t.Errorf("key (masked): got %q want %q", row[3], f.KeyMasked)
	}
	if row[4] != "src/client.go" {
		t.Errorf("source: got %q", row[4])
	}
	if row[5] != "17" {
		t.Errorf("line: got %q", row[5])
	}
	if row[6] != "2026-04-05T08:15:00Z" {
		t.Errorf("detected_at: got %q", row[6])
	}
	if row[7] != "false" {
		t.Errorf("verified: got %q", row[7])
	}
}

func TestCSVFormatter_Unmask(t *testing.T) {
	f := csvSampleFinding()
	var buf bytes.Buffer
	if err := (CSVFormatter{}).Format([]engine.Finding{f}, &buf, Options{Unmask: true}); err != nil {
		t.Fatalf("Format error: %v", err)
	}
	rows, err := csv.NewReader(&buf).ReadAll()
	if err != nil {
		t.Fatalf("csv parse error: %v", err)
	}
	if rows[1][3] != f.KeyValue {
		t.Errorf("key (unmasked): got %q want %q", rows[1][3], f.KeyValue)
	}
}

func TestCSVFormatter_QuotesCommaInSource(t *testing.T) {
	f := csvSampleFinding()
	f.Source = "path, with, commas.txt"
	var buf bytes.Buffer
	if err := (CSVFormatter{}).Format([]engine.Finding{f}, &buf, Options{}); err != nil {
		t.Fatalf("Format error: %v", err)
	}
	// Round-trip must preserve the comma-containing source verbatim.
	rows, err := csv.NewReader(&buf).ReadAll()
	if err != nil {
		t.Fatalf("csv parse error: %v", err)
	}
	if rows[1][4] != "path, with, commas.txt" {
		t.Errorf("source: got %q", rows[1][4])
	}
}

func TestCSVFormatter_VerifiedRow(t *testing.T) {
	f := csvSampleFinding()
	f.Verified = true
	f.VerifyStatus = "live"
	var buf bytes.Buffer
	if err := (CSVFormatter{}).Format([]engine.Finding{f}, &buf, Options{}); err != nil {
		t.Fatalf("Format error: %v", err)
	}
	rows, err := csv.NewReader(&buf).ReadAll()
	if err != nil {
		t.Fatalf("csv parse error: %v", err)
	}
	if rows[1][7] != "true" {
		t.Errorf("verified: got %q want true", rows[1][7])
	}
	if rows[1][8] != "live" {
		t.Errorf("verify_status: got %q want live", rows[1][8])
	}
}

func TestCSVFormatter_MultipleRowsIncrementID(t *testing.T) {
	a := csvSampleFinding()
	b := csvSampleFinding()
	b.ProviderName = "openai"
	var buf bytes.Buffer
	if err := (CSVFormatter{}).Format([]engine.Finding{a, b}, &buf, Options{}); err != nil {
		t.Fatalf("Format error: %v", err)
	}
	rows, err := csv.NewReader(&buf).ReadAll()
	if err != nil {
		t.Fatalf("csv parse error: %v", err)
	}
	if len(rows) != 3 {
		t.Fatalf("want 3 rows, got %d", len(rows))
	}
	if rows[1][0] != "0" || rows[2][0] != "1" {
		t.Errorf("ids: got %q,%q want 0,1", rows[1][0], rows[2][0])
	}
}
