package equipment

import (
	"bamort/database"
	"bamort/models"
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

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

	tests := []struct {
		name           string
		status         int
		message        string
		expectedStatus int
		expectedBody   map[string]string
	}{
		{
			name:           "Bad Request Error",
			status:         http.StatusBadRequest,
			message:        "Invalid request",
			expectedStatus: http.StatusBadRequest,
			expectedBody:   map[string]string{"error": "Invalid request"},
		},
		{
			name:           "Internal Server Error",
			status:         http.StatusInternalServerError,
			message:        "Database connection failed",
			expectedStatus: http.StatusInternalServerError,
			expectedBody:   map[string]string{"error": "Database connection failed"},
		},
		{
			name:           "Not Found Error",
			status:         http.StatusNotFound,
			message:        "Resource not found",
			expectedStatus: http.StatusNotFound,
			expectedBody:   map[string]string{"error": "Resource not found"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)

			respondWithError(c, tt.status, tt.message)

			assert.Equal(t, tt.expectedStatus, w.Code)

			var response map[string]string
			err := json.Unmarshal(w.Body.Bytes(), &response)
			assert.NoError(t, err)
			assert.Equal(t, tt.expectedBody, response)
		})
	}
}

func TestCreateAusruestung(t *testing.T) {
	database.SetupTestDB(true)
	gin.SetMode(gin.TestMode)

	tests := []struct {
		name           string
		payload        interface{}
		expectedStatus int
		shouldContain  string
	}{
		{
			name: "Valid Ausruestung Creation",
			payload: models.EqAusruestung{
				BamortCharTrait: models.BamortCharTrait{
					BamortBase: models.BamortBase{
						Name: "Test Sword",
					},
					CharacterID: 1,
					UserID:      1,
				},
				Magisch: models.Magisch{
					IstMagisch:  false,
					Abw:         0,
					Ausgebrannt: false,
				},
				Beschreibung:  "A test sword",
				Anzahl:        1,
				BeinhaltetIn:  "",
				ContainedIn:   0,
				ContainerType: "",
				Bonus:         0,
				Gewicht:       2.5,
				Wert:          100.0,
			},
			expectedStatus: http.StatusCreated,
			shouldContain:  "Test Sword",
		},
		{
			name:           "Invalid JSON",
			payload:        "invalid json",
			expectedStatus: http.StatusBadRequest,
			shouldContain:  "error",
		},
		{
			name:           "Empty JSON",
			payload:        map[string]interface{}{},
			expectedStatus: http.StatusCreated,
			shouldContain:  "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)

			var body bytes.Buffer
			if tt.name == "Invalid JSON" {
				body = *bytes.NewBufferString("invalid json")
			} else {
				jsonData, _ := json.Marshal(tt.payload)
				body = *bytes.NewBuffer(jsonData)
			}

			c.Request = httptest.NewRequest("POST", "/ausruestung", &body)
			c.Request.Header.Set("Content-Type", "application/json")

			CreateAusruestung(c)

			assert.Equal(t, tt.expectedStatus, w.Code)
			if tt.shouldContain != "" {
				assert.Contains(t, w.Body.String(), tt.shouldContain)
			}
		})
	}
}

func TestListAusruestung(t *testing.T) {
	database.SetupTestDB(true)
	gin.SetMode(gin.TestMode)

	// Create test data
	testAusruestung := models.EqAusruestung{
		BamortCharTrait: models.BamortCharTrait{
			BamortBase: models.BamortBase{
				Name: "Test Equipment",
			},
			CharacterID: 123,
			UserID:      1,
		},
		Magisch: models.Magisch{
			IstMagisch:  false,
			Abw:         0,
			Ausgebrannt: false,
		},
		Beschreibung:  "Test equipment description",
		Anzahl:        1,
		BeinhaltetIn:  "",
		ContainedIn:   0,
		ContainerType: "",
		Bonus:         0,
		Gewicht:       1.0,
		Wert:          50.0,
	}

	err := database.DB.Create(&testAusruestung).Error
	require.NoError(t, err)

	tests := []struct {
		name           string
		characterID    string
		expectedStatus int
		expectedCount  int
	}{
		{
			name:           "Valid Character ID",
			characterID:    "123",
			expectedStatus: http.StatusOK,
			expectedCount:  1,
		},
		{
			name:           "Non-existent Character ID",
			characterID:    "999",
			expectedStatus: http.StatusOK,
			expectedCount:  0,
		},
		{
			name:           "Invalid Character ID",
			characterID:    "invalid",
			expectedStatus: http.StatusOK,
			expectedCount:  0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)

			c.Params = gin.Params{
				{Key: "character_id", Value: tt.characterID},
			}

			ListAusruestung(c)

			assert.Equal(t, tt.expectedStatus, w.Code)

			var response []models.EqAusruestung
			err := json.Unmarshal(w.Body.Bytes(), &response)
			assert.NoError(t, err)
			assert.Len(t, response, tt.expectedCount)

			if tt.expectedCount > 0 {
				assert.Equal(t, "Test Equipment", response[0].Name)
				assert.Equal(t, uint(123), response[0].CharacterID)
			}
		})
	}
}

func TestUpdateAusruestung(t *testing.T) {
	database.SetupTestDB(true)
	gin.SetMode(gin.TestMode)

	// Create test data
	testAusruestung := models.EqAusruestung{
		BamortCharTrait: models.BamortCharTrait{
			BamortBase: models.BamortBase{
				Name: "Original Equipment",
			},
			CharacterID: 123,
			UserID:      1,
		},
		Magisch: models.Magisch{
			IstMagisch:  false,
			Abw:         0,
			Ausgebrannt: false,
		},
		Beschreibung:  "Original description",
		Anzahl:        1,
		BeinhaltetIn:  "",
		ContainedIn:   0,
		ContainerType: "",
		Bonus:         0,
		Gewicht:       1.0,
		Wert:          50.0,
	}

	err := database.DB.Create(&testAusruestung).Error
	require.NoError(t, err)

	tests := []struct {
		name           string
		ausruestungID  string
		payload        interface{}
		expectedStatus int
		shouldContain  string
	}{
		{
			name:          "Valid Update",
			ausruestungID: strconv.Itoa(int(testAusruestung.ID)),
			payload: map[string]interface{}{
				"name":         "Updated Equipment",
				"beschreibung": "Updated description",
				"wert":         75.0,
			},
			expectedStatus: http.StatusOK,
			shouldContain:  "Updated Equipment",
		},
		{
			name:          "Non-existent Ausruestung",
			ausruestungID: "999",
			payload: map[string]interface{}{
				"name": "Updated Equipment",
			},
			expectedStatus: http.StatusNotFound,
			shouldContain:  "error",
		},
		{
			name:           "Invalid JSON",
			ausruestungID:  strconv.Itoa(int(testAusruestung.ID)),
			payload:        "invalid json",
			expectedStatus: http.StatusBadRequest,
			shouldContain:  "error",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)

			c.Params = gin.Params{
				{Key: "ausruestung_id", Value: tt.ausruestungID},
			}

			var body bytes.Buffer
			if tt.name == "Invalid JSON" {
				body = *bytes.NewBufferString("invalid json")
			} else {
				jsonData, _ := json.Marshal(tt.payload)
				body = *bytes.NewBuffer(jsonData)
			}

			c.Request = httptest.NewRequest("PUT", "/ausruestung/"+tt.ausruestungID, &body)
			c.Request.Header.Set("Content-Type", "application/json")

			UpdateAusruestung(c)

			assert.Equal(t, tt.expectedStatus, w.Code)
			if tt.shouldContain != "" {
				assert.Contains(t, w.Body.String(), tt.shouldContain)
			}
		})
	}
}

func TestDeleteAusruestung(t *testing.T) {
	database.SetupTestDB(true)
	gin.SetMode(gin.TestMode)

	// Create test data
	testAusruestung := models.EqAusruestung{
		BamortCharTrait: models.BamortCharTrait{
			BamortBase: models.BamortBase{
				Name: "Equipment to Delete",
			},
			CharacterID: 123,
			UserID:      1,
		},
		Magisch: models.Magisch{
			IstMagisch:  false,
			Abw:         0,
			Ausgebrannt: false,
		},
		Beschreibung:  "Equipment for deletion test",
		Anzahl:        1,
		BeinhaltetIn:  "",
		ContainedIn:   0,
		ContainerType: "",
		Bonus:         0,
		Gewicht:       1.0,
		Wert:          50.0,
	}

	err := database.DB.Create(&testAusruestung).Error
	require.NoError(t, err)

	tests := []struct {
		name           string
		ausruestungID  string
		expectedStatus int
		shouldContain  string
	}{
		{
			name:           "Valid Deletion",
			ausruestungID:  strconv.Itoa(int(testAusruestung.ID)),
			expectedStatus: http.StatusOK,
			shouldContain:  "deleted successfully",
		},
		{
			name:           "Non-existent Ausruestung",
			ausruestungID:  "999",
			expectedStatus: http.StatusOK, // GORM doesn't fail on deleting non-existent records
			shouldContain:  "deleted successfully",
		},
		{
			name:           "Invalid Ausruestung ID",
			ausruestungID:  "invalid",
			expectedStatus: http.StatusInternalServerError,
			shouldContain:  "error",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)

			c.Params = gin.Params{
				{Key: "ausruestung_id", Value: tt.ausruestungID},
			}

			DeleteAusruestung(c)

			assert.Equal(t, tt.expectedStatus, w.Code)
			if tt.shouldContain != "" {
				assert.Contains(t, w.Body.String(), tt.shouldContain)
			}

			// For successful deletion, verify the record is actually deleted
			if tt.name == "Valid Deletion" && tt.expectedStatus == http.StatusOK {
				var count int64
				database.DB.Model(&models.EqAusruestung{}).Where("id = ?", tt.ausruestungID).Count(&count)
				assert.Equal(t, int64(0), count, "Equipment should be deleted from database")
			}
		})
	}
}
