package controllers

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"GoMembership/internal/constants"
	"GoMembership/internal/database"
	"GoMembership/internal/models"
	"GoMembership/pkg/logger"

	"github.com/gin-gonic/gin"
)

type RegisterSubscriptionTest struct {
	WantDBData   map[string]interface{}
	Input        string
	Name         string
	WantResponse int
	Assert       bool
}

type UpdateSubscriptionTest struct {
	WantDBData   map[string]interface{}
	Input        string
	Name         string
	WantResponse int
	Assert       bool
}

type DeleteSubscriptionTest struct {
	WantDBData   map[string]interface{}
	Input        string
	Name         string
	WantResponse int
	Assert       bool
}

type MockUserController struct {
	UserController // Embed the UserController
}

func (m *MockUserController) ExtractUserFromContext(c *gin.Context) (*models.User, error) {
	return &models.User{
		ID:        1,
		FirstName: "Admin",
		LastName:  "User",
		Email:     "admin@test.com",
		RoleID:    constants.Roles.Admin,
	}, nil
}

func setupMockAuth() {
	// Create and assign the mock controller
	mockController := &MockUserController{}
	Mc.UserController = mockController
}

func testMembershipController(t *testing.T) {

	setupMockAuth()
	tests := getSubscriptionRegistrationData()
	for _, tt := range tests {
		logger.Error.Print("==============================================================")
		logger.Error.Printf("MembershipController : %v", tt.Name)
		logger.Error.Print("==============================================================")
		t.Run(tt.Name, func(t *testing.T) {
			if err := runSingleTest(&tt); err != nil {
				t.Errorf("Test failed: %v", err.Error())
			}
		})
	}
	updateTests := getSubscriptionUpdateData()
	for _, tt := range updateTests {
		logger.Error.Print("==============================================================")
		logger.Error.Printf("Update SubscriptionData : %v", tt.Name)
		logger.Error.Print("==============================================================")
		t.Run(tt.Name, func(t *testing.T) {
			if err := runSingleTest(&tt); err != nil {
				t.Errorf("Test failed: %v", err.Error())
			}
		})
	}
	deleteTests := getSubscriptionDeleteData()
	for _, tt := range deleteTests {
		logger.Error.Print("==============================================================")
		logger.Error.Printf("Delete SubscriptionData : %v", tt.Name)
		logger.Error.Print("==============================================================")
		t.Run(tt.Name, func(t *testing.T) {
			if err := runSingleTest(&tt); err != nil {
				t.Errorf("Test failed: %v", err.Error())
			}
		})
	}
}

func (rt *RegisterSubscriptionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
	return GetMockedJSONContext([]byte(rt.Input), "api/subscription")
}

func (rt *RegisterSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
	Mc.RegisterSubscription(c)
}

func (rt *RegisterSubscriptionTest) ValidateResponse(w *httptest.ResponseRecorder) error {
	if w.Code != rt.WantResponse {
		return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, rt.WantResponse)
	}
	return nil
}

func (rt *RegisterSubscriptionTest) ValidateResult() error {
	return validateSubscription(rt.Assert, rt.WantDBData)
}

func validateSubscription(assert bool, wantDBData map[string]interface{}) error {
	subscriptions, err := Mc.Service.GetSubscriptions(wantDBData)
	if err != nil {
		return fmt.Errorf("Error in database ops: %#v", err)
	}
	if assert != (len(*subscriptions) != 0) {
		return fmt.Errorf("Subscription entry query didn't met expectation: %v != %#v", assert, *subscriptions)
	}
	return nil
}

func (ut *UpdateSubscriptionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
	return GetMockedJSONContext([]byte(ut.Input), "api/subscription/upsert")
}

func (ut *UpdateSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
	Mc.UpdateHandler(c)
}

func (ut *UpdateSubscriptionTest) ValidateResponse(w *httptest.ResponseRecorder) error {
	if w.Code != ut.WantResponse {
		return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, ut.WantResponse)
	}
	return nil
}

func (ut *UpdateSubscriptionTest) ValidateResult() error {
	return validateSubscription(ut.Assert, ut.WantDBData)
}

func (dt *DeleteSubscriptionTest) SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
	return GetMockedJSONContext([]byte(dt.Input), "api/subscription/delete")
}

func (dt *DeleteSubscriptionTest) RunHandler(c *gin.Context, router *gin.Engine) {
	Mc.DeleteSubscription(c)
}

func (dt *DeleteSubscriptionTest) ValidateResponse(w *httptest.ResponseRecorder) error {
	if w.Code != dt.WantResponse {
		return fmt.Errorf("Didn't get the expected response code: got: %v; expected: %v", w.Code, dt.WantResponse)
	}
	return nil
}

func (dt *DeleteSubscriptionTest) ValidateResult() error {
	return validateSubscription(dt.Assert, dt.WantDBData)
}

func getBaseSubscription() MembershipData {
	return MembershipData{
		// APIKey: config.Auth.APIKEY,
		Subscription: models.SubscriptionModel{
			Name:       "Premium",
			Details:    "A subscription detail",
			MonthlyFee: 12.0,
			HourlyRate: 14.0,
		},
	}
}
func customizeSubscription(customize func(MembershipData) MembershipData) MembershipData {
	subscription := getBaseSubscription()
	return customize(subscription)
}

func getSubscriptionRegistrationData() []RegisterSubscriptionTest {
	return []RegisterSubscriptionTest{
		{
			Name:         "Missing details should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"name": "Just a Subscription"},
			Assert:       false,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.Details = ""
					return subscription
				})),
		},
		{
			Name:         "Missing model name should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"name": ""},
			Assert:       false,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.Name = ""
					return subscription
				})),
		},
		{
			Name:         "Negative monthly fee should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"name": "Premium"},
			Assert:       false,
			Input: GenerateInputJSON(customizeSubscription(func(sub MembershipData) MembershipData {
				sub.Subscription.MonthlyFee = -10.0
				return sub
			})),
		},
		{
			Name:         "Negative hourly rate should fail",
			WantResponse: http.StatusBadRequest,
			WantDBData:   map[string]interface{}{"name": "Premium"},
			Assert:       false,
			Input: GenerateInputJSON(customizeSubscription(func(sub MembershipData) MembershipData {
				sub.Subscription.HourlyRate = -1.0
				return sub
			})),
		},
		{
			Name:         "correct entry should pass",
			WantResponse: http.StatusCreated,
			WantDBData:   map[string]interface{}{"name": "Premium"},
			Assert:       true,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.Conditions = "Some Condition"
					subscription.Subscription.IncludedPerYear = 0
					subscription.Subscription.IncludedPerMonth = 1
					return subscription
				})),
		},
		{
			Name:         "Duplicate subscription name should fail",
			WantResponse: http.StatusConflict,
			WantDBData:   map[string]interface{}{"name": "Premium"},
			Assert:       true, // The original subscription should still exist
			Input:        GenerateInputJSON(getBaseSubscription()),
		},
	}
}

func getSubscriptionUpdateData() []UpdateSubscriptionTest {
	return []UpdateSubscriptionTest{
		{
			Name:         "Modified Monthly Fee, should fail",
			WantResponse: http.StatusNotAcceptable,
			WantDBData:   map[string]interface{}{"name": "Premium", "monthly_fee": "12"},
			Assert:       true,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.MonthlyFee = 123.0
					return subscription
				})),
		},
		{
			Name:         "Missing ID, should fail",
			WantResponse: http.StatusNotAcceptable,
			WantDBData:   map[string]interface{}{"name": "Premium"},
			Assert:       true,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.ID = 0
					return subscription
				})),
		},
		{
			Name:         "Modified Hourly Rate, should fail",
			WantResponse: http.StatusNotAcceptable,
			WantDBData:   map[string]interface{}{"name": "Premium", "hourly_rate": "14"},
			Assert:       true,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.HourlyRate = 3254.0
					return subscription
				})),
		},
		{
			Name:         "IncludedPerYear changed, should fail",
			WantResponse: http.StatusNotAcceptable,
			WantDBData:   map[string]interface{}{"name": "Premium", "included_per_year": "0"},
			Assert:       true,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.IncludedPerYear = 9873.0
					return subscription
				})),
		},
		{
			Name:         "IncludedPerMonth changed, should fail",
			WantResponse: http.StatusNotAcceptable,
			WantDBData:   map[string]interface{}{"name": "Premium", "included_per_month": "1"},
			Assert:       true,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.IncludedPerMonth = 23415.0
					return subscription
				})),
		},
		{
			Name:         "Update non-existent subscription should fail",
			WantResponse: http.StatusNotAcceptable,
			WantDBData:   map[string]interface{}{"name": "NonExistentSubscription"},
			Assert:       false,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.Name = "NonExistentSubscription"
					return subscription
				})),
		},
		{
			Name:         "Correct Update should pass",
			WantResponse: http.StatusAccepted,
			WantDBData:   map[string]interface{}{"name": "Premium", "details": "Altered Details"},
			Assert:       true,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.Details = "Altered Details"
					subscription.Subscription.Conditions = "Some Condition"
					subscription.Subscription.IncludedPerYear = 0
					subscription.Subscription.IncludedPerMonth = 1
					return subscription
				})),
		},
	}
}

func getSubscriptionDeleteData() []DeleteSubscriptionTest {

	var premiumSub, basicSub models.SubscriptionModel
	database.DB.Where("name = ?", "Premium").First(&premiumSub)
	database.DB.Where("name = ?", "Basic").First(&basicSub)

	logger.Error.Printf("premiumSub.ID: %v", premiumSub.ID)
	logger.Error.Printf("basicSub.ID: %v", basicSub.ID)
	return []DeleteSubscriptionTest{
		{
			Name:         "Delete non-existent subscription should fail",
			WantResponse: http.StatusExpectationFailed,
			WantDBData:   map[string]interface{}{"name": "NonExistentSubscription"},
			Assert:       false,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.Name = "NonExistentSubscription"
					subscription.Subscription.ID = basicSub.ID
					return subscription
				})),
		},
		{
			Name:         "Delete subscription without name should fail",
			WantResponse: http.StatusExpectationFailed,
			WantDBData:   map[string]interface{}{"name": ""},
			Assert:       false,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.Name = ""
					subscription.Subscription.ID = basicSub.ID
					return subscription
				})),
		},
		{
			Name:         "Delete subscription with users should fail",
			WantResponse: http.StatusExpectationFailed,
			WantDBData:   map[string]interface{}{"name": "Basic"},
			Assert:       true,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.Name = "Basic"
					subscription.Subscription.ID = basicSub.ID
					return subscription
				})),
		},
		{
			Name:         "Delete valid subscription should succeed",
			WantResponse: http.StatusOK,
			WantDBData:   map[string]interface{}{"name": "Premium"},
			Assert:       false,
			Input: GenerateInputJSON(
				customizeSubscription(func(subscription MembershipData) MembershipData {
					subscription.Subscription.Name = "Premium"
					subscription.Subscription.ID = premiumSub.ID
					return subscription
				})),
		},
	}
}
