package handler

import (
	"baron-sso-backend/internal/domain"
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gofiber/fiber/v2"
	"github.com/stretchr/testify/require"
)

type recordingUpdateMeUserRepo struct {
	MockUserRepoForHandler
	updated  *domain.User
	loginIDs []domain.UserLoginID
}

func (r *recordingUpdateMeUserRepo) Update(ctx context.Context, user *domain.User) error {
	copied := *user
	r.updated = &copied
	return nil
}

func (r *recordingUpdateMeUserRepo) UpdateUserLoginIDs(ctx context.Context, userID string, loginIDs []domain.UserLoginID) error {
	r.loginIDs = append([]domain.UserLoginID(nil), loginIDs...)
	return nil
}

func TestUpdateMe_InvalidatesProfileCacheForTokenSession(t *testing.T) {
	token := "token-abc"
	identityID := "user-1"
	traits := map[string]interface{}{
		"email":           "qa@example.com",
		"name":            "QA User",
		"phone_number":    "+821012345678",
		"department":      "Old Dept",
		"affiliationType": "employee",
		"companyCode":     "",
		"role":            domain.RoleUser,
	}

	transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
		switch {
		case r.URL.Host == "kratos.test" &&
			r.URL.Path == "/sessions/whoami" &&
			r.Method == http.MethodGet:
			if r.Header.Get("X-Session-Token") != token {
				return httpResponse(r, http.StatusUnauthorized, `{"error":"invalid token"}`), nil
			}
			return httpJSONAny(r, http.StatusOK, map[string]interface{}{
				"identity": map[string]interface{}{
					"id":     identityID,
					"traits": traits,
				},
			}), nil

		case r.URL.Host == "kratos.test" &&
			r.URL.Path == "/admin/identities/"+identityID &&
			r.Method == http.MethodPut:
			var payload struct {
				Traits map[string]interface{} `json:"traits"`
			}
			if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
				return httpResponse(r, http.StatusBadRequest, `{"error":"invalid body"}`), nil
			}
			for k, v := range payload.Traits {
				traits[k] = v
			}
			return httpResponse(r, http.StatusOK, `{"ok":true}`), nil
		}

		return httpResponse(r, http.StatusNotFound, "not found"), nil
	})
	setDefaultHTTPClientForTest(t, transport)
	t.Setenv("KRATOS_PUBLIC_URL", "http://kratos.test")
	t.Setenv("KRATOS_ADMIN_URL", "http://kratos.test")

	redis := &mockRedisRepo{data: make(map[string]string)}
	h := &AuthHandler{
		RedisService: redis,
	}
	app := fiber.New()
	app.Get("/api/v1/user/me", h.GetMe)
	app.Put("/api/v1/user/me", h.UpdateMe)

	// 1) 첫 조회로 Old Dept가 캐시에 저장됨
	getReq1 := httptest.NewRequest(http.MethodGet, "/api/v1/user/me", nil)
	getReq1.Header.Set("Authorization", "Bearer "+token)
	getResp1, err := app.Test(getReq1, -1)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, getResp1.StatusCode)
	var profile1 map[string]interface{}
	require.NoError(t, json.NewDecoder(getResp1.Body).Decode(&profile1))
	require.Equal(t, "Old Dept", profile1["department"])

	// 2) 소속을 New Dept로 변경
	updateBody, _ := json.Marshal(map[string]string{
		"name":       "QA User",
		"phone":      "01012345678",
		"department": "New Dept",
	})
	updateReq := httptest.NewRequest(
		http.MethodPut,
		"/api/v1/user/me",
		bytes.NewReader(updateBody),
	)
	updateReq.Header.Set("Content-Type", "application/json")
	updateReq.Header.Set("Authorization", "Bearer "+token)
	updateResp, err := app.Test(updateReq, -1)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, updateResp.StatusCode)
	require.Equal(t, "New Dept", traits["department"])

	// 3) 새로고침 재조회 시 New Dept가 보여야 함(캐시 무효화 회귀 방지)
	getReq2 := httptest.NewRequest(http.MethodGet, "/api/v1/user/me", nil)
	getReq2.Header.Set("Authorization", "Bearer "+token)
	getResp2, err := app.Test(getReq2, -1)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, getResp2.StatusCode)
	var profile2 map[string]interface{}
	require.NoError(t, json.NewDecoder(getResp2.Body).Decode(&profile2))
	require.Equal(t, "New Dept", profile2["department"])
}

func TestUpdateMe_SyncsLocalReadModelFields(t *testing.T) {
	token := "token-sync"
	identityID := "user-sync"
	traits := map[string]interface{}{
		"email":           "sync@example.com",
		"name":            "Old Name",
		"phone_number":    "+821012345678",
		"department":      "Old Dept",
		"affiliationType": "employee",
		"companyCode":     "saman",
		"tenant_id":       "11111111-1111-1111-1111-111111111111",
		"role":            domain.RoleUser,
	}

	transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
		switch {
		case r.URL.Host == "kratos.test" &&
			r.URL.Path == "/sessions/whoami" &&
			r.Method == http.MethodGet:
			if r.Header.Get("X-Session-Token") != token {
				return httpResponse(r, http.StatusUnauthorized, `{"error":"invalid token"}`), nil
			}
			return httpJSONAny(r, http.StatusOK, map[string]interface{}{
				"identity": map[string]interface{}{
					"id":     identityID,
					"traits": traits,
				},
			}), nil

		case r.URL.Host == "kratos.test" &&
			r.URL.Path == "/admin/identities/"+identityID &&
			r.Method == http.MethodPut:
			var payload struct {
				Traits map[string]interface{} `json:"traits"`
			}
			if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
				return httpResponse(r, http.StatusBadRequest, `{"error":"invalid body"}`), nil
			}
			for k, v := range payload.Traits {
				traits[k] = v
			}
			return httpResponse(r, http.StatusOK, `{"ok":true}`), nil
		}

		return httpResponse(r, http.StatusNotFound, "not found"), nil
	})
	setDefaultHTTPClientForTest(t, transport)
	t.Setenv("KRATOS_PUBLIC_URL", "http://kratos.test")
	t.Setenv("KRATOS_ADMIN_URL", "http://kratos.test")

	redis := &mockRedisRepo{data: map[string]string{
		"verify_update_phone:" + identityID + ":+821087654321": "verified",
	}}
	userRepo := &recordingUpdateMeUserRepo{}
	h := &AuthHandler{
		RedisService: redis,
		UserRepo:     userRepo,
	}
	app := fiber.New()
	app.Put("/api/v1/user/me", h.UpdateMe)

	updateBody, _ := json.Marshal(map[string]interface{}{
		"name":       "New Name",
		"phone":      "01087654321",
		"department": "New Dept",
	})
	updateReq := httptest.NewRequest(
		http.MethodPut,
		"/api/v1/user/me",
		bytes.NewReader(updateBody),
	)
	updateReq.Header.Set("Content-Type", "application/json")
	updateReq.Header.Set("Authorization", "Bearer "+token)
	updateResp, err := app.Test(updateReq, -1)
	require.NoError(t, err)
	require.Equal(t, http.StatusOK, updateResp.StatusCode)

	require.NotNil(t, userRepo.updated)
	require.Equal(t, identityID, userRepo.updated.ID)
	require.Equal(t, "sync@example.com", userRepo.updated.Email)
	require.Equal(t, "New Name", userRepo.updated.Name)
	require.Equal(t, "+821087654321", userRepo.updated.Phone)
	require.Equal(t, "New Dept", userRepo.updated.Department)
	require.Empty(t, userRepo.updated.CompanyCode)
	require.NotNil(t, userRepo.updated.TenantID)
	require.Equal(t, "11111111-1111-1111-1111-111111111111", *userRepo.updated.TenantID)
}
