package table

import (
	"encoding/csv"
	"fmt"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestTable_RenderCSV(t *testing.T) {
	tw := NewWriter()
	tw.AppendHeader(testHeader)
	tw.AppendRows(testRows)
	tw.AppendRow(testRowMultiLine)
	tw.AppendRow(testRowTabs)
	tw.AppendFooter(testFooter)
	tw.SetCaption(testCaption)
	tw.SetTitle(testTitle1)

	compareOutput(t, tw.RenderCSV(), `
Game of Thrones
#,First Name,Last Name,Salary,
1,Arya,Stark,3000,
20,Jon,Snow,2000,"You know nothing, Jon Snow!"
300,Tyrion,Lannister,5000,
0,Winter,Is,0,"Coming.
The North Remembers!
This is known."
0,Valar,Morghulis,0,Faceless    Men
,,Total,10000,
A Song of Ice and Fire`)
}

func TestTable_RenderCSV_AutoIndex(t *testing.T) {
	tw := NewWriter()
	for rowIdx := 0; rowIdx < 10; rowIdx++ {
		row := make(Row, 10)
		for colIdx := 0; colIdx < 10; colIdx++ {
			row[colIdx] = fmt.Sprintf("%s%d", AutoIndexColumnID(colIdx), rowIdx+1)
		}
		tw.AppendRow(row)
	}
	for rowIdx := 0; rowIdx < 1; rowIdx++ {
		row := make(Row, 10)
		for colIdx := 0; colIdx < 10; colIdx++ {
			row[colIdx] = AutoIndexColumnID(colIdx) + "F"
		}
		tw.AppendFooter(row)
	}
	tw.SetAutoIndex(true)
	tw.SetStyle(StyleLight)

	compareOutput(t, tw.RenderCSV(), `
,A,B,C,D,E,F,G,H,I,J
1,A1,B1,C1,D1,E1,F1,G1,H1,I1,J1
2,A2,B2,C2,D2,E2,F2,G2,H2,I2,J2
3,A3,B3,C3,D3,E3,F3,G3,H3,I3,J3
4,A4,B4,C4,D4,E4,F4,G4,H4,I4,J4
5,A5,B5,C5,D5,E5,F5,G5,H5,I5,J5
6,A6,B6,C6,D6,E6,F6,G6,H6,I6,J6
7,A7,B7,C7,D7,E7,F7,G7,H7,I7,J7
8,A8,B8,C8,D8,E8,F8,G8,H8,I8,J8
9,A9,B9,C9,D9,E9,F9,G9,H9,I9,J9
10,A10,B10,C10,D10,E10,F10,G10,H10,I10,J10
,AF,BF,CF,DF,EF,FF,GF,HF,IF,JF`)
}

func TestTable_RenderCSV_Empty(t *testing.T) {
	tw := NewWriter()
	assert.Empty(t, tw.RenderCSV())
}

func TestTable_RenderCSV_HiddenColumns(t *testing.T) {
	tw := NewWriter()
	tw.AppendHeader(testHeader)
	tw.AppendRows(testRows)
	tw.AppendFooter(testFooter)

	// ensure sorting is done before hiding the columns
	tw.SortBy([]SortBy{
		{Name: "Salary", Mode: DscNumeric},
	})

	t.Run("every column hidden", func(t *testing.T) {
		tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{0, 1, 2, 3, 4}))

		compareOutput(t, tw.RenderCSV(), "")
	})

	t.Run("first column hidden", func(t *testing.T) {
		tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{0}))

		compareOutput(t, tw.RenderCSV(), `
First Name,Last Name,Salary,
>>Tyrion,Lannister<<,5013,
>>Arya,Stark<<,3013,
>>Jon,Snow<<,2013,"~You know nothing, Jon Snow!~"
,Total,10000,`)
	})

	t.Run("column hidden in the middle", func(t *testing.T) {
		tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{1}))

		compareOutput(t, tw.RenderCSV(), `
#,Last Name,Salary,
307,Lannister<<,5013,
8,Stark<<,3013,
27,Snow<<,2013,"~You know nothing, Jon Snow!~"
,Total,10000,`)
	})

	t.Run("last column hidden", func(t *testing.T) {
		tw.SetColumnConfigs(generateColumnConfigsWithHiddenColumns([]int{4}))

		compareOutput(t, tw.RenderCSV(), `
#,First Name,Last Name,Salary
307,>>Tyrion,Lannister<<,5013
8,>>Arya,Stark<<,3013
27,>>Jon,Snow<<,2013
,,Total,10000`)
	})
}

func TestTable_RenderCSV_Sorted(t *testing.T) {
	tw := NewWriter()
	tw.AppendHeader(testHeader)
	tw.AppendRows(testRows)
	tw.AppendRow(Row{11, "Sansa", "Stark", 6000})
	tw.AppendFooter(testFooter)
	tw.SortBy([]SortBy{{Name: "Last Name", Mode: Asc}, {Name: "First Name", Mode: Asc}})

	compareOutput(t, tw.RenderCSV(), `
#,First Name,Last Name,Salary,
300,Tyrion,Lannister,5000,
20,Jon,Snow,2000,"You know nothing, Jon Snow!"
1,Arya,Stark,3000,
11,Sansa,Stark,6000,
,,Total,10000,`)
}

func TestTable_RenderCSV_DoubleQuotes(t *testing.T) {
	tw := NewWriter()
	tw.AppendHeader(Row{"Name", "Quote"})
	tw.AppendRow(Row{"Jaime", `she said "hi" to me`})

	// double-quotes inside a quoted field are escaped per RFC 4180
	compareOutput(t, tw.RenderCSV(), `
Name,Quote
Jaime,"she said ""hi"" to me"`)
}

func TestTable_RenderCSV_FieldProtection(t *testing.T) {
	tw := NewWriter()
	tw.AppendHeader(Row{"Name", "Formula"})
	tw.AppendRow(Row{"Petyr", "=cmd|'/c calc'!A1"})
	tw.AppendRow(Row{"Varys", "@SUM(A1)"})
	tw.AppendRow(Row{"Bran", "+1234"})
	tw.AppendRow(Row{"Sansa", "-1234"})
	tw.AppendRow(Row{"Tormund", "1234"})

	// disabled by default
	compareOutput(t, tw.RenderCSV(), `
Name,Formula
Petyr,=cmd|'/c calc'!A1
Varys,@SUM(A1)
Bran,+1234
Sansa,-1234
Tormund,1234`)

	// formula triggers get neutralized with a single-quote prefix
	tw.Style().CSV.FieldProtection = true
	compareOutput(t, tw.RenderCSV(), `
Name,Formula
Petyr,'=cmd|'/c calc'!A1
Varys,'@SUM(A1)
Bran,'+1234
Sansa,'-1234
Tormund,1234`)
}

func TestTable_RenderCSV_RoundTrip(t *testing.T) {
	rows := []Row{
		{"Jon", `quoted "text" here`},
		{"Arya", "comma, separated"},
		{"Bran", "multi\nline"},
	}
	tw := NewWriter()
	tw.AppendHeader(Row{"Name", "Value"})
	tw.AppendRows(rows)

	// the output should be parseable by a standards-compliant CSV reader
	records, err := csv.NewReader(strings.NewReader(tw.RenderCSV())).ReadAll()
	assert.NoError(t, err)
	if assert.Len(t, records, len(rows)+1) {
		for idx, row := range rows {
			assert.Equal(t, fmt.Sprint(row[0]), records[idx+1][0])
			assert.Equal(t, fmt.Sprint(row[1]), records[idx+1][1])
		}
	}
}
