package accountapiimp_test

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/server/interface/api/srequest"
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/modules/mongodb"
	"github.com/testcontainers/testcontainers-go/wait"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

// TestSignupRequestSerialization tests JSON marshaling/unmarshaling with real MongoDB
func TestSignupRequestSerialization(t *testing.T) {
	if os.Getenv("RUN_DOCKER_TESTS") == "" {
		t.Skip("skipping: docker-dependent integration test (set RUN_DOCKER_TESTS=1 to enable)")
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	mongoContainer, err := mongodb.Run(ctx,
		"mongo:latest",
		mongodb.WithUsername("root"),
		mongodb.WithPassword("password"),
		testcontainers.WithWaitStrategy(wait.ForLog("Waiting for connections")),
	)
	require.NoError(t, err, "failed to start MongoDB container")
	defer func() {
		err := mongoContainer.Terminate(ctx)
		require.NoError(t, err, "failed to terminate MongoDB container")
	}()

	mongoURI, err := mongoContainer.ConnectionString(ctx)
	require.NoError(t, err, "failed to get MongoDB connection string")

	clientOptions := options.Client().ApplyURI(mongoURI)
	client, err := mongo.Connect(clientOptions)
	require.NoError(t, err, "failed to connect to MongoDB")
	defer func() {
		err := client.Disconnect(ctx)
		require.NoError(t, err, "failed to disconnect from MongoDB")
	}()

	db := client.Database("test_signup")
	collection := db.Collection("signup_requests")

	t.Run("StoreAndRetrieveSignupRequest", func(t *testing.T) {
		signupRequest := srequest.Signup{
			Account: model.AccountData{
				LoginData: model.LoginData{
					UserDataBase: model.UserDataBase{
						Login: "test@example.com",
					},
					Password: "TestPassword123!",
				},
				Describable: model.Describable{
					Name: "Test User",
				},
			},
			Organization: model.Describable{
				Name: "Test Organization",
			},
			OrganizationTimeZone: "UTC",
			OwnerRole: model.Describable{
				Name: "Owner",
			},
		}

		// Store in MongoDB
		result, err := collection.InsertOne(ctx, signupRequest)
		require.NoError(t, err)
		assert.NotNil(t, result.InsertedID)

		// Retrieve from MongoDB
		var retrieved srequest.Signup
		err = collection.FindOne(ctx, map[string]interface{}{"_id": result.InsertedID}).Decode(&retrieved)
		require.NoError(t, err)

		// Verify data integrity
		assert.Equal(t, signupRequest.Account.Login, retrieved.Account.Login)
		assert.Equal(t, signupRequest.Account.Name, retrieved.Account.Name)
		assert.Equal(t, signupRequest.Organization.Name, retrieved.Organization.Name)
		assert.Equal(t, signupRequest.OrganizationTimeZone, retrieved.OrganizationTimeZone)

	})
}

// TestSignupHTTPSerialization tests HTTP request/response serialization
func TestSignupHTTPSerialization(t *testing.T) {
	signupRequest := srequest.Signup{
		Account: model.AccountData{
			LoginData: model.LoginData{
				UserDataBase: model.UserDataBase{
					Login: "test@example.com",
				},
				Password: "TestPassword123!",
			},
			Describable: model.Describable{
				Name: "Test User",
			},
		},
		Organization: model.Describable{
			Name: "Test Organization",
		},
		OrganizationTimeZone: "UTC",
		OwnerRole: model.Describable{
			Name: "Owner",
		},
	}

	t.Run("ValidJSONRequest", func(t *testing.T) {
		// Serialize to JSON
		reqBody, err := json.Marshal(signupRequest)
		require.NoError(t, err)

		// Create HTTP request
		req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/signup", bytes.NewBuffer(reqBody))
		req.Header.Set("Content-Type", "application/json")

		// Parse the request body
		var parsedRequest srequest.Signup
		err = json.NewDecoder(req.Body).Decode(&parsedRequest)
		require.NoError(t, err)

		// Verify parsing
		assert.Equal(t, signupRequest.Account.Login, parsedRequest.Account.Login)
		assert.Equal(t, signupRequest.Account.Name, parsedRequest.Account.Name)
		assert.Equal(t, signupRequest.Organization.Name, parsedRequest.Organization.Name)
	})

	t.Run("UnicodeCharacters", func(t *testing.T) {
		unicodeRequest := signupRequest
		unicodeRequest.Account.Name = "Test 用户 Üser"
		unicodeRequest.Organization.Name = "测试 Organization"

		// Serialize to JSON
		reqBody, err := json.Marshal(unicodeRequest)
		require.NoError(t, err)

		// Parse back
		var parsedRequest srequest.Signup
		err = json.Unmarshal(reqBody, &parsedRequest)
		require.NoError(t, err)

		// Verify unicode characters are preserved
		assert.Equal(t, "Test 用户 Üser", parsedRequest.Account.Name)
		assert.Equal(t, "测试 Organization", parsedRequest.Organization.Name)
	})

	t.Run("InvalidJSONRequest", func(t *testing.T) {
		invalidJSON := `{"account": {"login": "test@example.com", "password": "invalid json structure`

		req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/signup", bytes.NewBufferString(invalidJSON))
		req.Header.Set("Content-Type", "application/json")

		var parsedRequest srequest.Signup
		err := json.NewDecoder(req.Body).Decode(&parsedRequest)
		assert.Error(t, err, "Should fail to parse invalid JSON")
	})
}

// TestAccountDataConversion tests conversion between request and model types
func TestAccountDataConversion(t *testing.T) {
	accountData := model.AccountData{
		LoginData: model.LoginData{
			UserDataBase: model.UserDataBase{
				Login: "test@example.com",
			},
			Password: "TestPassword123!",
		},
		Describable: model.Describable{
			Name: "Test User",
		},
	}

	t.Run("ToAccount", func(t *testing.T) {
		account := accountData.ToAccount()

		assert.Equal(t, accountData.Login, account.Login)
		assert.Equal(t, accountData.Password, account.Password)
		assert.Equal(t, accountData.Name, account.Name)

		// Verify the account has proper structure
		assert.NotNil(t, account)
		assert.IsType(t, &model.Account{}, account)
	})

	t.Run("PasswordHandling", func(t *testing.T) {
		account := accountData.ToAccount()

		// Original password should be preserved before validation
		assert.Equal(t, "TestPassword123!", account.Password)

		// Verify password is not empty
		assert.NotEmpty(t, account.Password)
	})
}
