//go:build e2e

package e2e_test

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

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

type KeygenFlowTestSuite struct {
	suite.Suite
	baseURL string
	client  *http.Client
}

func TestKeygenFlowSuite(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping e2e test in short mode")
	}
	suite.Run(t, new(KeygenFlowTestSuite))
}

func (s *KeygenFlowTestSuite) SetupSuite() {
	s.baseURL = os.Getenv("SESSION_COORDINATOR_URL")
	if s.baseURL == "" {
		s.baseURL = "http://localhost:8080"
	}

	s.client = &http.Client{
		Timeout: 30 * time.Second,
	}

	// Wait for service to be ready
	s.waitForService()
}

func (s *KeygenFlowTestSuite) waitForService() {
	maxRetries := 30
	for i := 0; i < maxRetries; i++ {
		resp, err := s.client.Get(s.baseURL + "/health")
		if err == nil && resp.StatusCode == http.StatusOK {
			resp.Body.Close()
			return
		}
		if resp != nil {
			resp.Body.Close()
		}
		time.Sleep(time.Second)
	}
	s.T().Fatal("Service not ready after waiting")
}

type CreateSessionRequest struct {
	SessionType string `json:"sessionType"`
	ThresholdT  int    `json:"thresholdT"`
	ThresholdN  int    `json:"thresholdN"`
	CreatedBy   string `json:"createdBy"`
}

type CreateSessionResponse struct {
	SessionID string `json:"sessionId"`
	JoinToken string `json:"joinToken"`
	Status    string `json:"status"`
}

type JoinSessionRequest struct {
	JoinToken  string `json:"joinToken"`
	PartyID    string `json:"partyId"`
	DeviceType string `json:"deviceType"`
	DeviceID   string `json:"deviceId"`
}

type JoinSessionResponse struct {
	SessionID   string `json:"sessionId"`
	PartyIndex  int    `json:"partyIndex"`
	Status      string `json:"status"`
	Participants []struct {
		PartyID string `json:"partyId"`
		Status  string `json:"status"`
	} `json:"participants"`
}

type SessionStatusResponse struct {
	SessionID    string `json:"sessionId"`
	Status       string `json:"status"`
	ThresholdT   int    `json:"thresholdT"`
	ThresholdN   int    `json:"thresholdN"`
	Participants []struct {
		PartyID    string `json:"partyId"`
		PartyIndex int    `json:"partyIndex"`
		Status     string `json:"status"`
	} `json:"participants"`
}

func (s *KeygenFlowTestSuite) TestCompleteKeygenFlow() {
	// Step 1: Create a new keygen session
	createReq := CreateSessionRequest{
		SessionType: "keygen",
		ThresholdT:  2,
		ThresholdN:  3,
		CreatedBy:   "e2e_test_user",
	}

	createResp := s.createSession(createReq)
	require.NotEmpty(s.T(), createResp.SessionID)
	require.NotEmpty(s.T(), createResp.JoinToken)
	assert.Equal(s.T(), "created", createResp.Status)

	sessionID := createResp.SessionID
	joinToken := createResp.JoinToken

	// Step 2: First party joins
	joinReq1 := JoinSessionRequest{
		JoinToken:  joinToken,
		PartyID:    "party_user_device",
		DeviceType: "iOS",
		DeviceID:   "device_001",
	}
	joinResp1 := s.joinSession(joinReq1)
	assert.Equal(s.T(), sessionID, joinResp1.SessionID)
	assert.Equal(s.T(), 0, joinResp1.PartyIndex)

	// Step 3: Second party joins
	joinReq2 := JoinSessionRequest{
		JoinToken:  joinToken,
		PartyID:    "party_server",
		DeviceType: "server",
		DeviceID:   "server_001",
	}
	joinResp2 := s.joinSession(joinReq2)
	assert.Equal(s.T(), sessionID, joinResp2.SessionID)
	assert.Equal(s.T(), 1, joinResp2.PartyIndex)

	// Step 4: Third party joins
	joinReq3 := JoinSessionRequest{
		JoinToken:  joinToken,
		PartyID:    "party_recovery",
		DeviceType: "recovery",
		DeviceID:   "recovery_001",
	}
	joinResp3 := s.joinSession(joinReq3)
	assert.Equal(s.T(), sessionID, joinResp3.SessionID)
	assert.Equal(s.T(), 2, joinResp3.PartyIndex)

	// Step 5: Check session status - should have all participants
	statusResp := s.getSessionStatus(sessionID)
	assert.Equal(s.T(), 3, len(statusResp.Participants))

	// Step 6: Mark parties as ready
	s.markPartyReady(sessionID, "party_user_device")
	s.markPartyReady(sessionID, "party_server")
	s.markPartyReady(sessionID, "party_recovery")

	// Step 7: Start the session
	s.startSession(sessionID)

	// Step 8: Verify session is in progress
	statusResp = s.getSessionStatus(sessionID)
	assert.Equal(s.T(), "in_progress", statusResp.Status)

	// Step 9: Report completion for all participants (simulate keygen completion)
	publicKey := []byte("test-group-public-key-from-keygen")
	s.reportCompletion(sessionID, "party_user_device", publicKey)
	s.reportCompletion(sessionID, "party_server", publicKey)
	s.reportCompletion(sessionID, "party_recovery", publicKey)

	// Step 10: Verify session is completed
	statusResp = s.getSessionStatus(sessionID)
	assert.Equal(s.T(), "completed", statusResp.Status)
}

func (s *KeygenFlowTestSuite) TestJoinSessionWithInvalidToken() {
	joinReq := JoinSessionRequest{
		JoinToken:  "invalid-token",
		PartyID:    "party_test",
		DeviceType: "iOS",
		DeviceID:   "device_test",
	}

	body, _ := json.Marshal(joinReq)
	resp, err := s.client.Post(
		s.baseURL+"/api/v1/sessions/join",
		"application/json",
		bytes.NewReader(body),
	)
	require.NoError(s.T(), err)
	defer resp.Body.Close()

	assert.Equal(s.T(), http.StatusUnauthorized, resp.StatusCode)
}

func (s *KeygenFlowTestSuite) TestGetNonExistentSession() {
	resp, err := s.client.Get(s.baseURL + "/api/v1/sessions/00000000-0000-0000-0000-000000000000")
	require.NoError(s.T(), err)
	defer resp.Body.Close()

	assert.Equal(s.T(), http.StatusNotFound, resp.StatusCode)
}

func (s *KeygenFlowTestSuite) TestExceedParticipantLimit() {
	// Create session with 2 participants max
	createReq := CreateSessionRequest{
		SessionType: "keygen",
		ThresholdT:  2,
		ThresholdN:  2,
		CreatedBy:   "e2e_test_user_limit",
	}

	createResp := s.createSession(createReq)
	joinToken := createResp.JoinToken

	// Join with 2 parties (should succeed)
	for i := 0; i < 2; i++ {
		joinReq := JoinSessionRequest{
			JoinToken:  joinToken,
			PartyID:    "party_" + string(rune('a'+i)),
			DeviceType: "test",
			DeviceID:   "device_" + string(rune('a'+i)),
		}
		s.joinSession(joinReq)
	}

	// Try to join with 3rd party (should fail)
	joinReq := JoinSessionRequest{
		JoinToken:  joinToken,
		PartyID:    "party_extra",
		DeviceType: "test",
		DeviceID:   "device_extra",
	}

	body, _ := json.Marshal(joinReq)
	resp, err := s.client.Post(
		s.baseURL+"/api/v1/sessions/join",
		"application/json",
		bytes.NewReader(body),
	)
	require.NoError(s.T(), err)
	defer resp.Body.Close()

	assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode)
}

// Helper methods

func (s *KeygenFlowTestSuite) createSession(req CreateSessionRequest) CreateSessionResponse {
	body, _ := json.Marshal(req)
	resp, err := s.client.Post(
		s.baseURL+"/api/v1/sessions",
		"application/json",
		bytes.NewReader(body),
	)
	require.NoError(s.T(), err)
	defer resp.Body.Close()

	require.Equal(s.T(), http.StatusCreated, resp.StatusCode)

	var result CreateSessionResponse
	err = json.NewDecoder(resp.Body).Decode(&result)
	require.NoError(s.T(), err)

	return result
}

func (s *KeygenFlowTestSuite) joinSession(req JoinSessionRequest) JoinSessionResponse {
	body, _ := json.Marshal(req)
	resp, err := s.client.Post(
		s.baseURL+"/api/v1/sessions/join",
		"application/json",
		bytes.NewReader(body),
	)
	require.NoError(s.T(), err)
	defer resp.Body.Close()

	require.Equal(s.T(), http.StatusOK, resp.StatusCode)

	var result JoinSessionResponse
	err = json.NewDecoder(resp.Body).Decode(&result)
	require.NoError(s.T(), err)

	return result
}

func (s *KeygenFlowTestSuite) getSessionStatus(sessionID string) SessionStatusResponse {
	resp, err := s.client.Get(s.baseURL + "/api/v1/sessions/" + sessionID)
	require.NoError(s.T(), err)
	defer resp.Body.Close()

	require.Equal(s.T(), http.StatusOK, resp.StatusCode)

	var result SessionStatusResponse
	err = json.NewDecoder(resp.Body).Decode(&result)
	require.NoError(s.T(), err)

	return result
}

func (s *KeygenFlowTestSuite) markPartyReady(sessionID, partyID string) {
	req := map[string]string{"partyId": partyID}
	body, _ := json.Marshal(req)

	httpReq, _ := http.NewRequest(
		http.MethodPut,
		s.baseURL+"/api/v1/sessions/"+sessionID+"/parties/"+partyID+"/ready",
		bytes.NewReader(body),
	)
	httpReq.Header.Set("Content-Type", "application/json")

	resp, err := s.client.Do(httpReq)
	require.NoError(s.T(), err)
	defer resp.Body.Close()

	require.Equal(s.T(), http.StatusOK, resp.StatusCode)
}

func (s *KeygenFlowTestSuite) startSession(sessionID string) {
	httpReq, _ := http.NewRequest(
		http.MethodPost,
		s.baseURL+"/api/v1/sessions/"+sessionID+"/start",
		nil,
	)

	resp, err := s.client.Do(httpReq)
	require.NoError(s.T(), err)
	defer resp.Body.Close()

	require.Equal(s.T(), http.StatusOK, resp.StatusCode)
}

func (s *KeygenFlowTestSuite) reportCompletion(sessionID string, partyID string, publicKey []byte) {
	req := map[string]interface{}{
		"party_id":   partyID,
		"public_key": string(publicKey),
	}
	body, _ := json.Marshal(req)

	httpReq, _ := http.NewRequest(
		http.MethodPost,
		s.baseURL+"/api/v1/sessions/"+sessionID+"/complete",
		bytes.NewReader(body),
	)
	httpReq.Header.Set("Content-Type", "application/json")

	resp, err := s.client.Do(httpReq)
	require.NoError(s.T(), err)
	defer resp.Body.Close()

	require.Equal(s.T(), http.StatusOK, resp.StatusCode)
}
