package controllers

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"strconv"
	"testing"
	"time"

	"log"

	"github.com/gin-gonic/gin"

	"GoMembership/internal/config"
	"GoMembership/internal/database"
	"GoMembership/internal/models"
	"GoMembership/internal/repositories"
	"GoMembership/internal/services"
	"GoMembership/internal/utils"
	"GoMembership/internal/validation"
	"GoMembership/pkg/logger"
)

type TestCase interface {
	SetupContext() (*gin.Context, *httptest.ResponseRecorder, *gin.Engine)
	RunHandler(*gin.Context, *gin.Engine)
	ValidateResponse(*httptest.ResponseRecorder) error
	ValidateResult() error
}

const (
	Host     = "127.0.0.1"
	Port int = 2525
)

type loginInput struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

var (
	Uc *UserController
	Mc *MembershipController
	Cc *ContactController
)

func TestSuite(t *testing.T) {
	_ = deleteTestDB("test.db")

	cwd, err := os.Getwd()
	if err != nil {
		log.Fatalf("Failed to get current working directory: %v", err)
	}

	// Build paths relative to the current working directory
	configFilePath := filepath.Join(cwd, "..", "..", "configs", "config.json")
	templateHTMLPath := filepath.Join(cwd, "..", "..", "templates", "html")
	templateMailPath := filepath.Join(cwd, "..", "..", "templates", "email")

	if err := os.Setenv("TEMPLATE_MAIL_PATH", templateMailPath); err != nil {
		log.Fatalf("Error setting environment variable: %v", err)
	}
	if err := os.Setenv("TEMPLATE_HTML_PATH", templateHTMLPath); err != nil {
		log.Fatalf("Error setting environment variable: %v", err)
	}
	if err := os.Setenv("CONFIG_FILE_PATH", configFilePath); err != nil {
		log.Fatalf("Error setting environment variable: %v", err)
	}
	if err := os.Setenv("SMTP_HOST", Host); err != nil {
		log.Fatalf("Error setting environment variable: %v", err)
	}
	if err := os.Setenv("SMTP_PORT", strconv.Itoa(Port)); err != nil {
		log.Fatalf("Error setting environment variable: %v", err)
	}
	if err := os.Setenv("BASE_URL", "http://"+Host+":2525"); err != nil {
		log.Fatalf("Error setting environment variable: %v", err)
	}
	if err := os.Setenv("DB_PATH", "test.db"); err != nil {
		log.Fatalf("Error setting environment variable: %v", err)
	}
	config.LoadConfig()
	if err := database.Open("test.db", config.Recipients.AdminEmail); err != nil {
		log.Fatalf("Failed to create DB: %#v", err)
	}
	utils.SMTPStart(Host, Port)
	emailService := services.NewEmailService(config.SMTP.Host, config.SMTP.Port, config.SMTP.User, config.SMTP.Password)
	var consentRepo repositories.ConsentRepositoryInterface = &repositories.ConsentRepository{}
	consentService := &services.ConsentService{Repo: consentRepo}

	var bankAccountRepo repositories.BankAccountRepositoryInterface = &repositories.BankAccountRepository{}
	bankAccountService := &services.BankAccountService{Repo: bankAccountRepo}

	var membershipRepo repositories.MembershipRepositoryInterface = &repositories.MembershipRepository{}
	var subscriptionRepo repositories.SubscriptionModelsRepositoryInterface = &repositories.SubscriptionModelsRepository{}
	membershipService := &services.MembershipService{Repo: membershipRepo, SubscriptionRepo: subscriptionRepo}

	var licenceRepo repositories.LicenceInterface = &repositories.LicenceRepository{}
	var userRepo repositories.UserRepositoryInterface = &repositories.UserRepository{}
	userService := &services.UserService{Repo: userRepo, Licences: licenceRepo}

	licenceService := &services.LicenceService{Repo: licenceRepo}

	Uc = &UserController{Service: userService, LicenceService: licenceService, EmailService: emailService, ConsentService: consentService, BankAccountService: bankAccountService, MembershipService: membershipService}
	Mc = &MembershipController{UserController: &MockUserController{}, Service: *membershipService}
	Cc = &ContactController{EmailService: emailService}

	if err := initSubscriptionPlans(); err != nil {
		log.Fatalf("Failed to init Subscription plans: %#v", err)
	}

	if err := initLicenceCategories(); err != nil {
		log.Fatalf("Failed to init Categories: %v", err)
	}
	validation.SetupValidators()
	t.Run("userController", func(t *testing.T) {
		testUserController(t)
	})

	t.Run("SQL_Injection", func(t *testing.T) {
		testSQLInjectionAttempt(t)
	})

	t.Run("contactController", func(t *testing.T) {
		testContactController(t)
	})

	t.Run("membershipController", func(t *testing.T) {
		testMembershipController(t)
	})

	t.Run("XSSAttempt", func(t *testing.T) {
		testXSSAttempt(t)
	})

	if err := utils.SMTPStop(); err != nil {
		log.Fatalf("Failed to stop SMTP Mockup Server: %#v", err)
	}

	// if err := deleteTestDB("test.db"); err != nil {
	// 	log.Fatalf("Failed to tear down DB: %#v", err)
	// }
}

func initLicenceCategories() error {
	categories := []models.Category{
		{Name: "AM"},
		{Name: "A1"},
		{Name: "A2"},
		{Name: "A"},
		{Name: "B"},
		{Name: "C1"},
		{Name: "C"},
		{Name: "D1"},
		{Name: "D"},
		{Name: "BE"},
		{Name: "C1E"},
		{Name: "CE"},
		{Name: "D1E"},
		{Name: "DE"},
		{Name: "T"},
		{Name: "L"},
	}
	for _, category := range categories {
		result := database.DB.Create(&category)
		if result.Error != nil {
			return result.Error
		}
	}

	return nil
}

func initSubscriptionPlans() error {
	subscriptions := []models.SubscriptionModel{
		{
			Name:       "Basic",
			Details:    "Test Plan",
			MonthlyFee: 2,
			HourlyRate: 3,
		},
		{
			Name:                    "additional",
			Details:                 "This plan needs another membership id to validate",
			RequiredMembershipField: "ParentMembershipID",
			MonthlyFee:              2,
			HourlyRate:              3,
		},
	}
	for _, subscription := range subscriptions {

		result := database.DB.Create(&subscription)
		if result.Error != nil {
			return result.Error
		}
	}

	return nil
}

func GetMockedJSONContext(jsonStr []byte, url string) (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	router := gin.New()

	// Load HTML templates
	router.LoadHTMLGlob(config.Templates.HTMLPath + "/*")

	var err error
	c.Request, err = http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	if err != nil {
		log.Fatalf("Failed to create new Request: %#v", err)
	}
	c.Request.Header.Set("Content-Type", "application/json")

	return c, w, router
}

func GetMockedFormContext(formData url.Values, url string) (*gin.Context, *httptest.ResponseRecorder, *gin.Engine) {
	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)

	router := gin.New()

	// Load HTML templates
	router.LoadHTMLGlob(config.Templates.HTMLPath + "/*")

	req, err := http.NewRequest("POST",
		url,
		bytes.NewBufferString(formData.Encode()))

	if err != nil {
		log.Fatalf("Failed to create new Request: %#v", err)
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	c.Request = req

	return c, w, router
}

func getBaseUser() models.User {
	return models.User{
		DateOfBirth:    time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC),
		FirstName:      "John",
		LastName:       "Doe",
		Email:          "john.doe@example.com",
		Address:        "Pablo Escobar Str. 4",
		ZipCode:        "25474",
		City:           "Hasloh",
		Phone:          "01738484993",
		BankAccount:    models.BankAccount{IBAN: "DE89370400440532013000"},
		Membership:     models.Membership{SubscriptionModel: models.SubscriptionModel{Name: "Basic"}},
		Licence:        nil,
		ProfilePicture: "",
		Password:       "password123",
		Company:        "",
	}
}

func deleteTestDB(dbPath string) error {
	err := os.Remove(dbPath)
	if err != nil {
		return err
	}
	return nil
}

func runSingleTest(tc TestCase) error {
	c, w, router := tc.SetupContext()
	tc.RunHandler(c, router)

	if err := tc.ValidateResponse(w); err != nil {
		return err
	}

	return tc.ValidateResult()
}

func GenerateInputJSON(aStruct interface{}) string {

	// Marshal the object into JSON
	jsonBytes, err := json.Marshal(aStruct)
	if err != nil {
		logger.Error.Fatalf("Couldn't generate JSON: %#v\nERROR: %#v", aStruct, err)
		return ""
	}
	return string(jsonBytes)
}
