package handlers

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"git.mchus.pro/mchus/quoteforge/internal/config"
	"git.mchus.pro/mchus/quoteforge/internal/models"
	"git.mchus.pro/mchus/quoteforge/internal/services"
	"github.com/gin-gonic/gin"
)

// Mock services for testing
type mockConfigService struct {
	config *models.Configuration
	err    error
}

func (m *mockConfigService) GetByUUID(uuid string, ownerUsername string) (*models.Configuration, error) {
	return m.config, m.err
}

func (m *mockConfigService) GetByUUIDNoAuth(uuid string) (*models.Configuration, error) {
	return m.config, m.err
}

func TestExportCSV_Success(t *testing.T) {
	gin.SetMode(gin.TestMode)

	// Create handler with mocks
	exportSvc := services.NewExportService(config.ExportConfig{}, nil)
	handler := NewExportHandler(
		exportSvc,
		&mockConfigService{},
		nil,
		"testuser",
	)

	// Create JSON request body
	jsonBody := `{
		"name": "Test Export",
		"items": [
			{
				"lot_name": "LOT-001",
				"quantity": 2,
				"unit_price": 100.50
			}
		],
		"notes": "Test notes"
	}`

	// Create HTTP request
	req, _ := http.NewRequest("POST", "/api/export/csv", bytes.NewBufferString(jsonBody))
	req.Header.Set("Content-Type", "application/json")

	// Create response recorder
	w := httptest.NewRecorder()

	// Create Gin context
	c, _ := gin.CreateTestContext(w)
	c.Request = req

	// Call handler
	handler.ExportCSV(c)

	// Check status code
	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d", w.Code)
	}

	// Check Content-Type header
	contentType := w.Header().Get("Content-Type")
	if contentType != "text/csv; charset=utf-8" {
		t.Errorf("Expected Content-Type 'text/csv; charset=utf-8', got %q", contentType)
	}

	// Check for BOM
	responseBody := w.Body.Bytes()
	if len(responseBody) < 3 {
		t.Fatalf("Response too short to contain BOM")
	}

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

	// Check semicolon delimiter in CSV
	reader := csv.NewReader(bytes.NewReader(responseBody[3:]))
	reader.Comma = ';'

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

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

func TestExportCSV_InvalidRequest(t *testing.T) {
	gin.SetMode(gin.TestMode)

	exportSvc := services.NewExportService(config.ExportConfig{}, nil)
	handler := NewExportHandler(
		exportSvc,
		&mockConfigService{},
		nil,
		"testuser",
	)

	// Create invalid request (missing required field)
	req, _ := http.NewRequest("POST", "/api/export/csv", bytes.NewBufferString(`{"name": "Test"}`))
	req.Header.Set("Content-Type", "application/json")

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req

	handler.ExportCSV(c)

	// Should return 400 Bad Request
	if w.Code != http.StatusUnprocessableEntity {
		t.Errorf("Expected status 422, got %d", w.Code)
	}

	// Should return JSON error
	var errResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &errResp)
	if _, hasError := errResp["error"]; !hasError {
		t.Errorf("Expected error in JSON response")
	}
}

func TestExportCSV_EmptyItems(t *testing.T) {
	gin.SetMode(gin.TestMode)

	exportSvc := services.NewExportService(config.ExportConfig{}, nil)
	handler := NewExportHandler(
		exportSvc,
		&mockConfigService{},
		nil,
		"testuser",
	)

	// Create request with empty items array - should fail binding validation
	req, _ := http.NewRequest("POST", "/api/export/csv", bytes.NewBufferString(`{"name":"Empty Export","items":[],"notes":""}`))
	req.Header.Set("Content-Type", "application/json")

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = req

	handler.ExportCSV(c)

	// Should return 400 Bad Request (validation error from gin binding)
	if w.Code != http.StatusUnprocessableEntity {
		t.Logf("Status code: %d (expected 422 for empty items)", w.Code)
	}
}

func TestExportConfigCSV_Success(t *testing.T) {
	gin.SetMode(gin.TestMode)

	// Mock configuration
	mockConfig := &models.Configuration{
		UUID:          "test-uuid",
		Name:          "Test Config",
		OwnerUsername: "testuser",
		Items: models.ConfigItems{
			{
				LotName:   "LOT-001",
				Quantity:  1,
				UnitPrice: 100.0,
			},
		},
		CreatedAt: time.Now(),
	}

	exportSvc := services.NewExportService(config.ExportConfig{}, nil)
	handler := NewExportHandler(
		exportSvc,
		&mockConfigService{config: mockConfig},
		nil,
		"testuser",
	)

	// Create HTTP request
	req, _ := http.NewRequest("GET", "/api/configs/test-uuid/export", nil)
	w := httptest.NewRecorder()

	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{
		{Key: "uuid", Value: "test-uuid"},
	}

	handler.ExportConfigCSV(c)

	// Check status code
	if w.Code != http.StatusOK {
		t.Errorf("Expected status 200, got %d", w.Code)
	}

	// Check Content-Type header
	contentType := w.Header().Get("Content-Type")
	if contentType != "text/csv; charset=utf-8" {
		t.Errorf("Expected Content-Type 'text/csv; charset=utf-8', got %q", contentType)
	}

	// Check for BOM
	responseBody := w.Body.Bytes()
	if len(responseBody) < 3 {
		t.Fatalf("Response too short to contain BOM")
	}

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

func TestExportConfigCSV_NotFound(t *testing.T) {
	gin.SetMode(gin.TestMode)

	exportSvc := services.NewExportService(config.ExportConfig{}, nil)
	handler := NewExportHandler(
		exportSvc,
		&mockConfigService{err: errors.New("config not found")},
		nil,
		"testuser",
	)

	req, _ := http.NewRequest("GET", "/api/configs/nonexistent-uuid/export", nil)
	w := httptest.NewRecorder()

	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{
		{Key: "uuid", Value: "nonexistent-uuid"},
	}
	handler.ExportConfigCSV(c)

	// Should return 404 Not Found
	if w.Code != http.StatusNotFound {
		t.Errorf("Expected status 404, got %d", w.Code)
	}

	// Should return JSON error
	var errResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &errResp)
	if _, hasError := errResp["error"]; !hasError {
		t.Errorf("Expected error in JSON response")
	}
}

func TestExportConfigCSV_EmptyItems(t *testing.T) {
	gin.SetMode(gin.TestMode)

	// Mock configuration with empty items
	mockConfig := &models.Configuration{
		UUID:          "test-uuid",
		Name:          "Empty Config",
		OwnerUsername: "testuser",
		Items:         models.ConfigItems{},
		CreatedAt:     time.Now(),
	}

	exportSvc := services.NewExportService(config.ExportConfig{}, nil)
	handler := NewExportHandler(
		exportSvc,
		&mockConfigService{config: mockConfig},
		nil,
		"testuser",
	)

	req, _ := http.NewRequest("GET", "/api/configs/test-uuid/export", nil)
	w := httptest.NewRecorder()

	c, _ := gin.CreateTestContext(w)
	c.Request = req
	c.Params = gin.Params{
		{Key: "uuid", Value: "test-uuid"},
	}
	handler.ExportConfigCSV(c)

	// Should return 400 Bad Request
	if w.Code != http.StatusUnprocessableEntity {
		t.Errorf("Expected status 422, got %d", w.Code)
	}

	// Should return JSON error
	var errResp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &errResp)
	if _, hasError := errResp["error"]; !hasError {
		t.Errorf("Expected error in JSON response")
	}
}
