package base_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/android-sms-gateway/server/internal/sms-gateway/handlers/base"
	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
	"go.uber.org/zap/zaptest"
)

type testRequestBody struct {
	Name string `json:"name" validate:"required"`
	Age  int    `json:"age" validate:"required"`
}

type testRequestBodyNoValidate struct {
	Name string `json:"name" validate:"required"`
	Age  int    `json:"age" validate:"required"`
}

type testRequestQuery struct {
	Name string `query:"name" validate:"required"`
	Age  int    `query:"age" validate:"required"`
}

type testRequestParams struct {
	ID   string `params:"id" validate:"required"`
	Name string `params:"name" validate:"required"`
}

func (t *testRequestBody) Validate() error {
	if t.Age < 18 {
		return fmt.Errorf("must be at least 18 years old")
	}
	return nil
}

func (t *testRequestQuery) Validate() error {
	if t.Age < 18 {
		return fmt.Errorf("must be at least 18 years old")
	}
	return nil
}

func (t *testRequestParams) Validate() error {
	if t.ID == "invalid" {
		return fmt.Errorf("invalid ID")
	}
	return nil
}

func TestHandler_BodyParserValidator(t *testing.T) {
	logger := zaptest.NewLogger(t)
	validate := validator.New()

	handler := &base.Handler{
		Logger:    logger,
		Validator: validate,
	}

	app := fiber.New()
	app.Post("/test", func(c *fiber.Ctx) error {
		var body testRequestBody
		return handler.BodyParserValidator(c, &body)
	})
	app.Post("/test2", func(c *fiber.Ctx) error {
		var body testRequestBodyNoValidate
		return handler.BodyParserValidator(c, &body)
	})

	tests := []struct {
		description    string
		path           string
		payload        any
		expectedStatus int
	}{
		{
			description:    "Valid request body",
			path:           "/test",
			payload:        &testRequestBody{Name: "John Doe", Age: 25},
			expectedStatus: fiber.StatusOK,
		},
		{
			description:    "Invalid request body - missing name",
			path:           "/test",
			payload:        &testRequestBody{Age: 25},
			expectedStatus: fiber.StatusBadRequest,
		},
		{
			description:    "Invalid request body - age too low",
			path:           "/test",
			payload:        &testRequestBody{Name: "John Doe", Age: 17},
			expectedStatus: fiber.StatusBadRequest,
		},
		{
			description:    "Valid request body - no validation",
			path:           "/test2",
			payload:        &testRequestBodyNoValidate{Name: "John Doe", Age: 17},
			expectedStatus: fiber.StatusOK,
		},
		{
			description:    "No request body",
			path:           "/test",
			payload:        nil,
			expectedStatus: fiber.StatusBadRequest,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			var req *http.Request
			if test.payload != nil {
				bodyBytes, _ := json.Marshal(test.payload)
				req = httptest.NewRequest("POST", test.path, bytes.NewReader(bodyBytes))
				req.Header.Set("Content-Type", "application/json")
			} else {
				req = httptest.NewRequest("POST", test.path, nil)
			}

			resp, err := app.Test(req)
			if err != nil {
				t.Fatalf("app.Test failed: %v", err)
			}
			if test.expectedStatus != resp.StatusCode {
				t.Errorf("Expected status code %d, got %d", test.expectedStatus, resp.StatusCode)
			}
		})
	}
}

func TestHandler_QueryParserValidator(t *testing.T) {
	logger := zaptest.NewLogger(t)
	validate := validator.New()

	handler := &base.Handler{
		Logger:    logger,
		Validator: validate,
	}

	app := fiber.New()
	app.Get("/test", func(c *fiber.Ctx) error {
		var query testRequestQuery
		return handler.QueryParserValidator(c, &query)
	})

	tests := []struct {
		description    string
		path           string
		expectedStatus int
	}{
		{
			description:    "Invalid query parameters - non-integer age",
			path:           "/test?name=John&age=abc",
			expectedStatus: fiber.StatusBadRequest,
		},
		{
			description:    "Valid query parameters",
			path:           "/test?name=John&age=25",
			expectedStatus: fiber.StatusOK,
		},
		{
			description:    "Invalid query parameters - missing name",
			path:           "/test?age=25",
			expectedStatus: fiber.StatusBadRequest,
		},
		{
			description:    "Invalid query parameters - age too low",
			path:           "/test?name=John&age=17",
			expectedStatus: fiber.StatusBadRequest,
		},
		{
			description:    "Invalid query parameters - missing age",
			path:           "/test?name=John",
			expectedStatus: fiber.StatusBadRequest,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			req := httptest.NewRequest("GET", test.path, nil)

			resp, err := app.Test(req)
			if err != nil {
				t.Fatalf("app.Test failed: %v", err)
			}
			if test.expectedStatus != resp.StatusCode {
				t.Errorf("Expected status code %d, got %d", test.expectedStatus, resp.StatusCode)
			}
		})
	}
}

func TestHandler_ParamsParserValidator(t *testing.T) {
	logger := zaptest.NewLogger(t)
	validate := validator.New()

	handler := &base.Handler{
		Logger:    logger,
		Validator: validate,
	}

	app := fiber.New()
	app.Get("/test/:id/:name", func(c *fiber.Ctx) error {
		var params testRequestParams
		return handler.ParamsParserValidator(c, &params)
	})

	tests := []struct {
		description    string
		path           string
		expectedStatus int
	}{
		{
			description:    "Valid path parameters",
			path:           "/test/123/John",
			expectedStatus: fiber.StatusOK,
		},
		{
			description:    "Invalid path parameters - missing id",
			path:           "/test//John",
			expectedStatus: fiber.StatusNotFound,
		},
		{
			description:    "Invalid path parameters - missing name",
			path:           "/test/123/",
			expectedStatus: fiber.StatusNotFound,
		},
		{
			description:    "Invalid path parameters - invalid ID",
			path:           "/test/invalid/John",
			expectedStatus: fiber.StatusBadRequest,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			req := httptest.NewRequest("GET", test.path, nil)

			resp, err := app.Test(req)
			if err != nil {
				t.Fatalf("app.Test failed: %v", err)
			}
			if test.expectedStatus != resp.StatusCode {
				t.Errorf("Expected status code %d, got %d", test.expectedStatus, resp.StatusCode)
			}
		})
	}
}

func TestHandler_ValidateStruct(t *testing.T) {
	logger := zaptest.NewLogger(t)
	validate := validator.New()

	// Test with validator
	handlerWithValidator := &base.Handler{
		Logger:    logger,
		Validator: validate,
	}

	// Test without validator
	handlerWithoutValidator := &base.Handler{
		Logger:    logger,
		Validator: nil,
	}

	tests := []struct {
		description    string
		handler        *base.Handler
		input          any
		expectedStatus int
	}{
		{
			description:    "Valid struct with validator",
			handler:        handlerWithValidator,
			input:          &testRequestBody{Name: "John Doe", Age: 25},
			expectedStatus: fiber.StatusOK,
		},
		{
			description:    "Invalid struct with validator - missing required field",
			handler:        handlerWithValidator,
			input:          &testRequestBody{Age: 25},
			expectedStatus: fiber.StatusBadRequest,
		},
		{
			description:    "Invalid struct with validator - custom validation fails",
			handler:        handlerWithValidator,
			input:          &testRequestBody{Name: "John Doe", Age: 17},
			expectedStatus: fiber.StatusBadRequest,
		},
		{
			description:    "Valid struct without validator",
			handler:        handlerWithoutValidator,
			input:          &testRequestBody{Name: "John Doe", Age: 25},
			expectedStatus: fiber.StatusOK,
		},
		{
			description:    "Invalid struct without validator - custom validation fails",
			handler:        handlerWithoutValidator,
			input:          &testRequestBody{Name: "John Doe", Age: 17},
			expectedStatus: fiber.StatusBadRequest,
		},
		{
			description:    "Valid struct with Validatable interface",
			handler:        handlerWithValidator,
			input:          &testRequestQuery{Name: "John", Age: 25},
			expectedStatus: fiber.StatusOK,
		},
		{
			description:    "Invalid struct with Validatable interface",
			handler:        handlerWithValidator,
			input:          &testRequestQuery{Name: "John", Age: 17},
			expectedStatus: fiber.StatusBadRequest,
		},
	}

	for _, test := range tests {
		t.Run(test.description, func(t *testing.T) {
			err := test.handler.ValidateStruct(test.input)

			if test.expectedStatus == fiber.StatusOK && err != nil {
				t.Errorf("Expected no error, got %v", err)
			}

			if test.expectedStatus == fiber.StatusBadRequest && err == nil {
				t.Errorf("Expected error, got nil")
			}
		})
	}
}
