package services

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

	"git.mchus.pro/mchus/quoteforge/internal/config"
	"git.mchus.pro/mchus/quoteforge/internal/models"
)

func newTestProjectData(items []ExportItem, article string, serverCount int) *ProjectExportData {
	var unitTotal float64
	for _, item := range items {
		unitTotal += item.UnitPrice * float64(item.Quantity)
	}
	if serverCount < 1 {
		serverCount = 1
	}
	return &ProjectExportData{
		Configs: []ConfigExportBlock{
			{
				Article:     article,
				ServerCount: serverCount,
				UnitPrice:   unitTotal,
				Items:       items,
			},
		},
		CreatedAt: time.Now(),
	}
}

func TestToCSV_UTF8BOM(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)

	data := newTestProjectData([]ExportItem{
		{
			LotName:    "LOT-001",
			Category:   "CAT",
			Quantity:   1,
			UnitPrice:  100.0,
			TotalPrice: 100.0,
		},
	}, "TEST-ARTICLE", 1)

	var buf bytes.Buffer
	if err := svc.ToCSV(&buf, data); err != nil {
		t.Fatalf("ToCSV failed: %v", err)
	}

	csvBytes := buf.Bytes()
	if len(csvBytes) < 3 {
		t.Fatalf("CSV too short to contain BOM")
	}

	expectedBOM := []byte{0xEF, 0xBB, 0xBF}
	actualBOM := csvBytes[:3]
	if !bytes.Equal(actualBOM, expectedBOM) {
		t.Errorf("UTF-8 BOM mismatch. Expected %v, got %v", expectedBOM, actualBOM)
	}
}

func TestToCSV_SemicolonDelimiter(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)

	data := newTestProjectData([]ExportItem{
		{
			LotName:    "LOT-001",
			Category:   "CAT",
			Quantity:   2,
			UnitPrice:  100.50,
			TotalPrice: 201.00,
		},
	}, "TEST-ARTICLE", 1)

	var buf bytes.Buffer
	if err := svc.ToCSV(&buf, data); err != nil {
		t.Fatalf("ToCSV failed: %v", err)
	}

	csvBytes := buf.Bytes()
	reader := csv.NewReader(bytes.NewReader(csvBytes[3:]))
	reader.Comma = ';'

	// Read header
	header, err := reader.Read()
	if err != nil {
		t.Fatalf("Failed to read header: %v", err)
	}

	if len(header) != 8 {
		t.Errorf("Expected 8 columns, got %d", len(header))
	}

	expectedHeader := []string{"Line", "Type", "p/n", "Description", "Qty (1 pcs.)", "Qty (total)", "Price (1 pcs.)", "Price (total)"}
	for i, col := range expectedHeader {
		if i < len(header) && header[i] != col {
			t.Errorf("Column %d: expected %q, got %q", i, col, header[i])
		}
	}

	// Read server row
	serverRow, err := reader.Read()
	if err != nil {
		t.Fatalf("Failed to read server row: %v", err)
	}
	if serverRow[0] != "10" {
		t.Errorf("Expected line number 10, got %s", serverRow[0])
	}
	if serverRow[2] != "TEST-ARTICLE" {
		t.Errorf("Expected article TEST-ARTICLE, got %s", serverRow[2])
	}

	// Read component row
	itemRow, err := reader.Read()
	if err != nil {
		t.Fatalf("Failed to read item row: %v", err)
	}
	if itemRow[2] != "LOT-001" {
		t.Errorf("Lot name mismatch: expected LOT-001, got %s", itemRow[2])
	}
	if itemRow[4] != "2" {
		t.Errorf("Quantity mismatch: expected 2, got %s", itemRow[4])
	}
	if itemRow[6] != "100,5" {
		t.Errorf("Unit price mismatch: expected 100,5, got %s", itemRow[6])
	}
}

func TestToCSV_ServerRow(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)

	data := newTestProjectData([]ExportItem{
		{LotName: "LOT-001", Category: "CAT", Quantity: 1, UnitPrice: 100.0, TotalPrice: 100.0},
		{LotName: "LOT-002", Category: "CAT", Quantity: 2, UnitPrice: 50.0, TotalPrice: 100.0},
	}, "DL380-ART", 10)

	var buf bytes.Buffer
	if err := svc.ToCSV(&buf, data); err != nil {
		t.Fatalf("ToCSV failed: %v", err)
	}

	csvBytes := buf.Bytes()
	reader := csv.NewReader(bytes.NewReader(csvBytes[3:]))
	reader.Comma = ';'

	// Skip header
	reader.Read()

	// Read server row
	serverRow, err := reader.Read()
	if err != nil {
		t.Fatalf("Failed to read server row: %v", err)
	}

	if serverRow[0] != "10" {
		t.Errorf("Expected line 10, got %s", serverRow[0])
	}
	if serverRow[2] != "DL380-ART" {
		t.Errorf("Expected article DL380-ART, got %s", serverRow[2])
	}
	if serverRow[5] != "10" {
		t.Errorf("Expected server count 10, got %s", serverRow[5])
	}
	// UnitPrice = 100 + 100 = 200
	if serverRow[6] != "200" {
		t.Errorf("Expected unit price 200, got %s", serverRow[6])
	}
	// TotalPrice = 200 * 10 = 2000
	if serverRow[7] != "2 000" {
		t.Errorf("Expected total price '2 000', got %q", serverRow[7])
	}
}

func TestToCSV_CategorySorting(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)

	data := newTestProjectData([]ExportItem{
		{LotName: "LOT-001", Category: "CAT-A", Quantity: 1, UnitPrice: 100.0, TotalPrice: 100.0},
		{LotName: "LOT-002", Category: "CAT-C", Quantity: 1, UnitPrice: 100.0, TotalPrice: 100.0},
		{LotName: "LOT-003", Category: "CAT-B", Quantity: 1, UnitPrice: 100.0, TotalPrice: 100.0},
	}, "ART", 1)

	var buf bytes.Buffer
	if err := svc.ToCSV(&buf, data); err != nil {
		t.Fatalf("ToCSV failed: %v", err)
	}

	csvBytes := buf.Bytes()
	reader := csv.NewReader(bytes.NewReader(csvBytes[3:]))
	reader.Comma = ';'

	// Skip header and server row
	reader.Read()
	reader.Read()

	// Without category repo, items maintain original order
	row1, _ := reader.Read()
	if row1[2] != "LOT-001" {
		t.Errorf("Expected LOT-001 first, got %s", row1[2])
	}

	row2, _ := reader.Read()
	if row2[2] != "LOT-002" {
		t.Errorf("Expected LOT-002 second, got %s", row2[2])
	}

	row3, _ := reader.Read()
	if row3[2] != "LOT-003" {
		t.Errorf("Expected LOT-003 third, got %s", row3[2])
	}
}

func TestToCSV_EmptyData(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)

	data := &ProjectExportData{
		Configs:   []ConfigExportBlock{},
		CreatedAt: time.Now(),
	}

	var buf bytes.Buffer
	if err := svc.ToCSV(&buf, data); err != nil {
		t.Fatalf("ToCSV failed: %v", err)
	}

	csvBytes := buf.Bytes()
	reader := csv.NewReader(bytes.NewReader(csvBytes[3:]))
	reader.Comma = ';'

	header, err := reader.Read()
	if err != nil {
		t.Fatalf("Failed to read header: %v", err)
	}

	if len(header) != 8 {
		t.Errorf("Expected 8 columns, got %d", len(header))
	}

	// No more rows expected
	_, err = reader.Read()
	if err != io.EOF {
		t.Errorf("Expected EOF after header, got: %v", err)
	}
}

func TestToCSVBytes_BackwardCompat(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)

	data := newTestProjectData([]ExportItem{
		{LotName: "LOT-001", Category: "CAT", Quantity: 1, UnitPrice: 100.0, TotalPrice: 100.0},
	}, "ART", 1)

	csvBytes, err := svc.ToCSVBytes(data)
	if err != nil {
		t.Fatalf("ToCSVBytes failed: %v", err)
	}

	if len(csvBytes) < 3 {
		t.Fatalf("CSV bytes too short")
	}

	expectedBOM := []byte{0xEF, 0xBB, 0xBF}
	actualBOM := csvBytes[:3]
	if !bytes.Equal(actualBOM, expectedBOM) {
		t.Errorf("UTF-8 BOM mismatch in ToCSVBytes")
	}
}

func TestToCSV_WriterError(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)

	data := newTestProjectData([]ExportItem{
		{LotName: "LOT-001", Category: "CAT", Quantity: 1, UnitPrice: 100.0, TotalPrice: 100.0},
	}, "ART", 1)

	failingWriter := &failingWriter{}

	if err := svc.ToCSV(failingWriter, data); err == nil {
		t.Errorf("Expected error from failing writer, got nil")
	}
}

func TestToCSV_MultipleBlocks(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)

	data := &ProjectExportData{
		Configs: []ConfigExportBlock{
			{
				Article:     "ART-1",
				ServerCount: 2,
				UnitPrice:   500.0,
				Items: []ExportItem{
					{LotName: "LOT-A", Category: "CPU", Quantity: 1, UnitPrice: 500.0, TotalPrice: 500.0},
				},
			},
			{
				Article:     "ART-2",
				ServerCount: 3,
				UnitPrice:   1000.0,
				Items: []ExportItem{
					{LotName: "LOT-B", Category: "MEM", Quantity: 2, UnitPrice: 500.0, TotalPrice: 1000.0},
				},
			},
		},
		CreatedAt: time.Now(),
	}

	var buf bytes.Buffer
	if err := svc.ToCSV(&buf, data); err != nil {
		t.Fatalf("ToCSV failed: %v", err)
	}

	csvBytes := buf.Bytes()
	reader := csv.NewReader(bytes.NewReader(csvBytes[3:]))
	reader.Comma = ';'
	reader.FieldsPerRecord = -1 // allow variable fields

	// Header
	reader.Read()

	// Block 1: server row
	srv1, _ := reader.Read()
	if srv1[0] != "10" {
		t.Errorf("Block 1 line: expected 10, got %s", srv1[0])
	}
	if srv1[7] != "1 000" {
		t.Errorf("Block 1 total: expected '1 000', got %q", srv1[7])
	}

	// Block 1: component row
	comp1, _ := reader.Read()
	if comp1[2] != "LOT-A" {
		t.Errorf("Block 1 component: expected LOT-A, got %s", comp1[2])
	}

	// Separator row
	sep, _ := reader.Read()
	allEmpty := true
	for _, v := range sep {
		if v != "" {
			allEmpty = false
		}
	}
	if !allEmpty {
		t.Errorf("Expected empty separator row, got %v", sep)
	}

	// Block 2: server row
	srv2, _ := reader.Read()
	if srv2[0] != "20" {
		t.Errorf("Block 2 line: expected 20, got %s", srv2[0])
	}
	if srv2[7] != "3 000" {
		t.Errorf("Block 2 total: expected '3 000', got %q", srv2[7])
	}
}

func TestProjectToExportData_SortsByLine(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)

	configs := []models.Configuration{
		{
			UUID:        "cfg-1",
			Line:        30,
			Article:     "ART-30",
			ServerCount: 1,
			Items:       models.ConfigItems{{LotName: "LOT-30", Quantity: 1, UnitPrice: 300}},
			CreatedAt:   time.Now().Add(-1 * time.Hour),
		},
		{
			UUID:        "cfg-2",
			Line:        10,
			Article:     "ART-10",
			ServerCount: 1,
			Items:       models.ConfigItems{{LotName: "LOT-10", Quantity: 1, UnitPrice: 100}},
			CreatedAt:   time.Now().Add(-2 * time.Hour),
		},
		{
			UUID:        "cfg-3",
			Line:        20,
			Article:     "ART-20",
			ServerCount: 1,
			Items:       models.ConfigItems{{LotName: "LOT-20", Quantity: 1, UnitPrice: 200}},
			CreatedAt:   time.Now().Add(-3 * time.Hour),
		},
	}

	data := svc.ProjectToExportData(configs)
	if len(data.Configs) != 3 {
		t.Fatalf("expected 3 blocks, got %d", len(data.Configs))
	}
	if data.Configs[0].Article != "ART-10" || data.Configs[0].Line != 10 {
		t.Fatalf("first block must be line 10, got article=%s line=%d", data.Configs[0].Article, data.Configs[0].Line)
	}
	if data.Configs[1].Article != "ART-20" || data.Configs[1].Line != 20 {
		t.Fatalf("second block must be line 20, got article=%s line=%d", data.Configs[1].Article, data.Configs[1].Line)
	}
	if data.Configs[2].Article != "ART-30" || data.Configs[2].Line != 30 {
		t.Fatalf("third block must be line 30, got article=%s line=%d", data.Configs[2].Article, data.Configs[2].Line)
	}
}

func TestFormatPriceWithSpace(t *testing.T) {
	tests := []struct {
		input    float64
		expected string
	}{
		{0, "0"},
		{100, "100"},
		{1000, "1 000"},
		{10470, "10 470"},
		{104700, "104 700"},
		{1000000, "1 000 000"},
	}

	for _, tt := range tests {
		result := formatPriceWithSpace(tt.input)
		if result != tt.expected {
			t.Errorf("formatPriceWithSpace(%v): expected %q, got %q", tt.input, tt.expected, result)
		}
	}
}

func TestFormatPriceComma(t *testing.T) {
	tests := []struct {
		input    float64
		expected string
	}{
		{100.0, "100"},
		{2074.5, "2074,5"},
		{100.50, "100,5"},
		{99.99, "99,99"},
		{0, "0"},
	}

	for _, tt := range tests {
		result := formatPriceComma(tt.input)
		if result != tt.expected {
			t.Errorf("formatPriceComma(%v): expected %q, got %q", tt.input, tt.expected, result)
		}
	}
}

func TestToPricingCSV_UsesSelectedColumns(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)
	data := &ProjectPricingExportData{
		Configs: []ProjectPricingExportConfig{
			{
				Name:        "Config A",
				Article:     "ART-1",
				Line:        10,
				ServerCount: 2,
				Rows: []ProjectPricingExportRow{
					{
						LotDisplay:  "LOT_A +1",
						VendorPN:    "PN-001",
						Description: "Bundle row",
						Quantity:    2,
						BOMTotal:    floatPtr(2400.5),
						Estimate:    floatPtr(2000),
						Stock:       floatPtr(1800.25),
					},
				},
			},
		},
		CreatedAt: time.Now(),
	}
	opts := ProjectPricingExportOptions{
		IncludeLOT:      true,
		IncludeBOM:      true,
		IncludeEstimate: true,
		IncludeStock:    true,
	}

	var buf bytes.Buffer
	if err := svc.ToPricingCSV(&buf, data, opts); err != nil {
		t.Fatalf("ToPricingCSV failed: %v", err)
	}

	reader := csv.NewReader(bytes.NewReader(buf.Bytes()[3:]))
	reader.Comma = ';'
	reader.FieldsPerRecord = -1

	header, err := reader.Read()
	if err != nil {
		t.Fatalf("read header row: %v", err)
	}
	expectedHeader := []string{"Line Item", "LOT", "PN вендора", "Описание", "Кол-во", "BOM", "Estimate", "Stock"}
	for i, want := range expectedHeader {
		if header[i] != want {
			t.Fatalf("header[%d]: expected %q, got %q", i, want, header[i])
		}
	}

	summary, err := reader.Read()
	if err != nil {
		t.Fatalf("read summary row: %v", err)
	}
	expectedSummary := []string{"10", "", "ART-1", "Config A", "2", "2 400,50", "2 000,00", "1 800,25"}
	for i, want := range expectedSummary {
		if summary[i] != want {
			t.Fatalf("summary[%d]: expected %q, got %q", i, want, summary[i])
		}
	}

	row, err := reader.Read()
	if err != nil {
		t.Fatalf("read data row: %v", err)
	}
	expectedRow := []string{"", "LOT_A +1", "PN-001", "Bundle row", "2", "2 400,50", "2 000,00", "1 800,25"}
	for i, want := range expectedRow {
		if row[i] != want {
			t.Fatalf("row[%d]: expected %q, got %q", i, want, row[i])
		}
	}
}

func TestProjectToPricingExportData_UsesCartRowsWithoutBOM(t *testing.T) {
	svc := NewExportService(config.ExportConfig{}, nil)
	configs := []models.Configuration{
		{
			UUID:        "cfg-1",
			Name:        "Config A",
			Article:     "ART-1",
			ServerCount: 1,
			Items: models.ConfigItems{
				{LotName: "LOT_A", Quantity: 2, UnitPrice: 300},
			},
			CreatedAt: time.Now(),
		},
	}

	data, err := svc.ProjectToPricingExportData(configs, ProjectPricingExportOptions{
		IncludeLOT:      true,
		IncludeEstimate: true,
	})
	if err != nil {
		t.Fatalf("ProjectToPricingExportData failed: %v", err)
	}
	if len(data.Configs) != 1 || len(data.Configs[0].Rows) != 1 {
		t.Fatalf("unexpected rows count: %+v", data.Configs)
	}
	row := data.Configs[0].Rows[0]
	if row.LotDisplay != "LOT_A" {
		t.Fatalf("expected LOT_A, got %q", row.LotDisplay)
	}
	if row.VendorPN != "—" {
		t.Fatalf("expected vendor dash, got %q", row.VendorPN)
	}
	if row.Estimate == nil || *row.Estimate != 600 {
		t.Fatalf("expected estimate total 600, got %+v", row.Estimate)
	}
}

// failingWriter always returns an error
type failingWriter struct{}

func (fw *failingWriter) Write(p []byte) (int, error) {
	return 0, io.EOF
}
