package handlerregression

import (
	"baron-sso-backend/internal/domain"
	"baron-sso-backend/internal/handler"
	"baron-sso-backend/internal/service"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"maps"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/gofiber/fiber/v2"
)

type roundTripFunc func(req *http.Request) (*http.Response, error)

func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
	return f(req)
}

type mockSecretRepo struct {
	secrets map[string]string
}

func (m *mockSecretRepo) Upsert(ctx context.Context, clientID, secret string) error {
	if m.secrets == nil {
		m.secrets = make(map[string]string)
	}
	m.secrets[clientID] = secret
	return nil
}

func (m *mockSecretRepo) GetByID(ctx context.Context, clientID string) (string, error) {
	return m.secrets[clientID], nil
}

func (m *mockSecretRepo) Delete(ctx context.Context, clientID string) error {
	delete(m.secrets, clientID)
	return nil
}

type mockRedisRepo struct {
	data map[string]string
}

func (m *mockRedisRepo) Set(key, value string, exp time.Duration) error {
	if m.data == nil {
		m.data = make(map[string]string)
	}
	m.data[key] = value
	return nil
}

func (m *mockRedisRepo) Get(key string) (string, error) {
	v, ok := m.data[key]
	if !ok {
		return "", fmt.Errorf("not found")
	}
	return v, nil
}

func (m *mockRedisRepo) Delete(key string) error {
	delete(m.data, key)
	return nil
}

func (m *mockRedisRepo) StoreVerificationCode(p, c string) error      { return nil }
func (m *mockRedisRepo) GetVerificationCode(p string) (string, error) { return "", nil }
func (m *mockRedisRepo) DeleteVerificationCode(p string) error        { return nil }

func httpJSONAny(r *http.Request, code int, payload any) *http.Response {
	body, _ := json.Marshal(payload)
	return &http.Response{
		StatusCode: code,
		Header:     http.Header{"Content-Type": []string{"application/json"}},
		Body:       io.NopCloser(bytes.NewReader(body)),
		Request:    r,
	}
}

func newDevHandlerApp(h *handler.DevHandler) *fiber.App {
	app := fiber.New()
	app.Use(func(c *fiber.Ctx) error {
		c.Locals("user_profile", &domain.UserProfileResponse{ID: "test-user", Role: domain.RoleSuperAdmin})
		return c.Next()
	})
	app.Post("/api/v1/dev/clients", h.CreateClient)
	app.Get("/api/v1/dev/clients/:id", h.GetClient)
	app.Put("/api/v1/dev/clients/:id", h.UpdateClient)
	app.Post("/api/v1/dev/clients/:id/secret/rotate", h.RotateClientSecret)
	return app
}

func decodeClientSecret(t *testing.T, resp *http.Response) string {
	t.Helper()

	var payload struct {
		Client struct {
			ClientSecret string `json:"clientSecret"`
		} `json:"client"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	return payload.Client.ClientSecret
}

func TestGetClient_ClientSecretFallbackPaths(t *testing.T) {
	tests := []struct {
		name                string
		hydraClient         map[string]any
		initialRedis        map[string]string
		initialSecrets      map[string]string
		expectedSecret      string
		expectedRedisAfter  string
		expectRedisAfterSet bool
	}{
		{
			name: "uses hydra client_secret directly",
			hydraClient: map[string]any{
				"client_id":                  "client-direct",
				"client_name":                "Direct App",
				"client_secret":              "hydra-secret",
				"redirect_uris":              []string{"https://direct.example.com/callback"},
				"grant_types":                []string{"authorization_code", "refresh_token"},
				"response_types":             []string{"code"},
				"scope":                      "openid profile",
				"token_endpoint_auth_method": "client_secret_basic",
				"metadata":                   map[string]any{"status": "active"},
			},
			expectedSecret: "hydra-secret",
		},
		{
			name: "falls back to metadata client_secret",
			hydraClient: map[string]any{
				"client_id":                  "client-metadata",
				"client_name":                "Metadata App",
				"redirect_uris":              []string{"https://metadata.example.com/callback"},
				"grant_types":                []string{"authorization_code", "refresh_token"},
				"response_types":             []string{"code"},
				"scope":                      "openid profile",
				"token_endpoint_auth_method": "client_secret_basic",
				"metadata": map[string]any{
					"status":        "active",
					"client_secret": "metadata-secret",
				},
			},
			expectedSecret: "metadata-secret",
		},
		{
			name: "falls back to redis cache",
			hydraClient: map[string]any{
				"client_id":                  "client-redis",
				"client_name":                "Redis App",
				"redirect_uris":              []string{"https://redis.example.com/callback"},
				"grant_types":                []string{"authorization_code", "refresh_token"},
				"response_types":             []string{"code"},
				"scope":                      "openid profile",
				"token_endpoint_auth_method": "client_secret_basic",
				"metadata":                   map[string]any{"status": "active"},
			},
			initialRedis:   map[string]string{"client_secret:client-redis": "redis-secret"},
			expectedSecret: "redis-secret",
		},
		{
			name: "falls back to postgres and warms redis",
			hydraClient: map[string]any{
				"client_id":                  "client-postgres",
				"client_name":                "Postgres App",
				"redirect_uris":              []string{"https://postgres.example.com/callback"},
				"grant_types":                []string{"authorization_code", "refresh_token"},
				"response_types":             []string{"code"},
				"scope":                      "openid profile",
				"token_endpoint_auth_method": "client_secret_basic",
				"metadata":                   map[string]any{"status": "active"},
			},
			initialSecrets:      map[string]string{"client-postgres": "postgres-secret"},
			expectedSecret:      "postgres-secret",
			expectedRedisAfter:  "postgres-secret",
			expectRedisAfterSet: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
				if r.Method == http.MethodGet && r.URL.Path == "/clients/"+tt.hydraClient["client_id"].(string) {
					return httpJSONAny(r, http.StatusOK, tt.hydraClient), nil
				}
				return httpJSONAny(r, http.StatusNotFound, nil), nil
			})

			secretRepo := &mockSecretRepo{secrets: map[string]string{}}
			maps.Copy(secretRepo.secrets, tt.initialSecrets)

			redisRepo := &mockRedisRepo{data: map[string]string{}}
			maps.Copy(redisRepo.data, tt.initialRedis)

			h := &handler.DevHandler{
				Hydra: &service.HydraAdminService{
					AdminURL:   "http://hydra.test",
					PublicURL:  "http://hydra.public",
					HTTPClient: &http.Client{Transport: transport},
				},
				SecretRepo: secretRepo,
				Redis:      redisRepo,
			}

			app := newDevHandlerApp(h)

			clientID := tt.hydraClient["client_id"].(string)
			req := httptest.NewRequest(http.MethodGet, "/api/v1/dev/clients/"+clientID, nil)
			resp, err := app.Test(req, -1)
			if err != nil {
				t.Fatalf("get request failed: %v", err)
			}
			if resp.StatusCode != http.StatusOK {
				t.Fatalf("expected get 200, got %d", resp.StatusCode)
			}

			if secret := decodeClientSecret(t, resp); secret != tt.expectedSecret {
				t.Fatalf("expected secret %q, got %q", tt.expectedSecret, secret)
			}

			if tt.expectRedisAfterSet {
				redisSecret, err := redisRepo.Get("client_secret:" + clientID)
				if err != nil {
					t.Fatalf("expected warmed redis secret, got error: %v", err)
				}
				if redisSecret != tt.expectedRedisAfter {
					t.Fatalf("expected warmed redis secret %q, got %q", tt.expectedRedisAfter, redisSecret)
				}
			}
		})
	}
}

func TestCreateClient_PersistsSecretForLaterDetailFetch(t *testing.T) {
	transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
		if r.Method == http.MethodPost && r.URL.Path == "/clients" {
			return httpJSONAny(r, http.StatusCreated, map[string]any{
				"client_id":                  "client-created",
				"client_name":                "Created App",
				"client_secret":              "created-secret",
				"redirect_uris":              []string{"https://created.example.com/callback"},
				"grant_types":                []string{"authorization_code", "refresh_token"},
				"response_types":             []string{"code"},
				"scope":                      "openid profile",
				"token_endpoint_auth_method": "client_secret_basic",
				"metadata":                   map[string]any{"status": "active"},
			}), nil
		}
		if r.Method == http.MethodGet && r.URL.Path == "/clients/client-created" {
			return httpJSONAny(r, http.StatusOK, map[string]any{
				"client_id":                  "client-created",
				"client_name":                "Created App",
				"redirect_uris":              []string{"https://created.example.com/callback"},
				"grant_types":                []string{"authorization_code", "refresh_token"},
				"response_types":             []string{"code"},
				"scope":                      "openid profile",
				"token_endpoint_auth_method": "client_secret_basic",
				"metadata":                   map[string]any{"status": "active"},
			}), nil
		}
		return httpJSONAny(r, http.StatusNotFound, nil), nil
	})

	secretRepo := &mockSecretRepo{secrets: make(map[string]string)}
	redisRepo := &mockRedisRepo{data: make(map[string]string)}

	h := &handler.DevHandler{
		Hydra: &service.HydraAdminService{
			AdminURL:   "http://hydra.test",
			PublicURL:  "http://hydra.public",
			HTTPClient: &http.Client{Transport: transport},
		},
		SecretRepo: secretRepo,
		Redis:      redisRepo,
	}

	app := newDevHandlerApp(h)

	createBody, _ := json.Marshal(map[string]any{
		"name":         "Created App",
		"type":         "private",
		"redirectUris": []string{"https://created.example.com/callback"},
		"scopes":       []string{"openid", "profile"},
	})
	createReq := httptest.NewRequest(http.MethodPost, "/api/v1/dev/clients", bytes.NewReader(createBody))
	createReq.Header.Set("Content-Type", "application/json")

	createResp, err := app.Test(createReq, -1)
	if err != nil {
		t.Fatalf("create request failed: %v", err)
	}
	if createResp.StatusCode != http.StatusCreated {
		t.Fatalf("expected create 201, got %d", createResp.StatusCode)
	}

	if secret := decodeClientSecret(t, createResp); secret != "created-secret" {
		t.Fatalf("expected create secret created-secret, got %q", secret)
	}

	storedSecret, _ := secretRepo.GetByID(context.Background(), "client-created")
	if storedSecret != "created-secret" {
		t.Fatalf("expected postgres secret created-secret, got %q", storedSecret)
	}

	redisSecret, err := redisRepo.Get("client_secret:client-created")
	if err != nil {
		t.Fatalf("expected redis secret after create, got error: %v", err)
	}
	if redisSecret != "created-secret" {
		t.Fatalf("expected redis secret created-secret, got %q", redisSecret)
	}

	getReq := httptest.NewRequest(http.MethodGet, "/api/v1/dev/clients/client-created", nil)
	getResp, err := app.Test(getReq, -1)
	if err != nil {
		t.Fatalf("get request failed: %v", err)
	}
	if getResp.StatusCode != http.StatusOK {
		t.Fatalf("expected get 200, got %d", getResp.StatusCode)
	}

	if secret := decodeClientSecret(t, getResp); secret != "created-secret" {
		t.Fatalf("expected detail secret created-secret, got %q", secret)
	}
}

func TestRotateClientSecret_PersistsForLaterDetailFetch(t *testing.T) {
	getCount := 0

	transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
		if r.Method == http.MethodGet && r.URL.Path == "/clients/client-rotate" {
			getCount++
			return httpJSONAny(r, http.StatusOK, map[string]any{
				"client_id":                  "client-rotate",
				"client_name":                "Rotate App",
				"redirect_uris":              []string{"https://rotate.example.com/callback"},
				"grant_types":                []string{"authorization_code", "refresh_token"},
				"response_types":             []string{"code"},
				"scope":                      "openid profile",
				"token_endpoint_auth_method": "client_secret_basic",
				"metadata":                   map[string]any{"status": "active"},
			}), nil
		}
		if r.Method == http.MethodPut && r.URL.Path == "/clients/client-rotate" {
			return httpJSONAny(r, http.StatusOK, map[string]any{
				"client_id":                  "client-rotate",
				"client_name":                "Rotate App",
				"redirect_uris":              []string{"https://rotate.example.com/callback"},
				"grant_types":                []string{"authorization_code", "refresh_token"},
				"response_types":             []string{"code"},
				"scope":                      "openid profile",
				"token_endpoint_auth_method": "client_secret_basic",
				"metadata":                   map[string]any{"status": "active"},
			}), nil
		}
		return httpJSONAny(r, http.StatusNotFound, nil), nil
	})

	secretRepo := &mockSecretRepo{secrets: make(map[string]string)}
	redisRepo := &mockRedisRepo{data: make(map[string]string)}

	h := &handler.DevHandler{
		Hydra: &service.HydraAdminService{
			AdminURL:   "http://hydra.test",
			PublicURL:  "http://hydra.public",
			HTTPClient: &http.Client{Transport: transport},
		},
		SecretRepo: secretRepo,
		Redis:      redisRepo,
	}

	app := newDevHandlerApp(h)

	rotateReq := httptest.NewRequest(http.MethodPost, "/api/v1/dev/clients/client-rotate/secret/rotate", nil)
	rotateResp, err := app.Test(rotateReq, -1)
	if err != nil {
		t.Fatalf("rotate request failed: %v", err)
	}
	if rotateResp.StatusCode != http.StatusOK {
		t.Fatalf("expected rotate 200, got %d", rotateResp.StatusCode)
	}

	rotatedSecret := decodeClientSecret(t, rotateResp)
	if rotatedSecret == "" {
		t.Fatalf("expected rotated secret to be present")
	}

	storedSecret, _ := secretRepo.GetByID(context.Background(), "client-rotate")
	if storedSecret != rotatedSecret {
		t.Fatalf("expected postgres secret %q, got %q", rotatedSecret, storedSecret)
	}

	redisSecret, err := redisRepo.Get("client_secret:client-rotate")
	if err != nil {
		t.Fatalf("expected redis secret after rotate, got error: %v", err)
	}
	if redisSecret != rotatedSecret {
		t.Fatalf("expected redis secret %q, got %q", rotatedSecret, redisSecret)
	}

	getReq := httptest.NewRequest(http.MethodGet, "/api/v1/dev/clients/client-rotate", nil)
	getResp, err := app.Test(getReq, -1)
	if err != nil {
		t.Fatalf("get request failed: %v", err)
	}
	if getResp.StatusCode != http.StatusOK {
		t.Fatalf("expected get 200, got %d", getResp.StatusCode)
	}

	if secret := decodeClientSecret(t, getResp); secret != rotatedSecret {
		t.Fatalf("expected detail secret %q, got %q", rotatedSecret, secret)
	}
}

func TestUpdateClient_HeadlessLoginSecretPersistsForLaterDetailFetch(t *testing.T) {
	getCount := 0

	transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
		if r.Method == http.MethodGet && r.URL.Path == "/clients/client-headless-login" {
			getCount++
			if getCount == 1 {
				return httpJSONAny(r, http.StatusOK, map[string]any{
					"client_id":                  "client-headless-login",
					"client_name":                "Headless Login Before",
					"redirect_uris":              []string{"https://before.example.com/callback"},
					"grant_types":                []string{"authorization_code", "refresh_token"},
					"response_types":             []string{"code"},
					"scope":                      "openid profile",
					"token_endpoint_auth_method": "none",
					"metadata": map[string]any{
						"status": "active",
					},
				}), nil
			}

			return httpJSONAny(r, http.StatusOK, map[string]any{
				"client_id":                  "client-headless-login",
				"client_name":                "Headless Login After",
				"redirect_uris":              []string{"https://headless.example.com/callback"},
				"grant_types":                []string{"authorization_code", "refresh_token"},
				"response_types":             []string{"code"},
				"scope":                      "openid profile",
				"token_endpoint_auth_method": "private_key_jwt",
				"jwks_uri":                   "https://headless.example.com/jwks.json",
				"metadata": map[string]any{
					"status":                     "active",
					"headless_login_enabled":     true,
					"request_object_signing_alg": "RS256",
				},
			}), nil
		}

		if r.Method == http.MethodPut && r.URL.Path == "/clients/client-headless-login" {
			return httpJSONAny(r, http.StatusOK, map[string]any{
				"client_id":                  "client-headless-login",
				"client_name":                "Headless Login After",
				"client_secret":              "headless-secret",
				"redirect_uris":              []string{"https://headless.example.com/callback"},
				"grant_types":                []string{"authorization_code", "refresh_token"},
				"response_types":             []string{"code"},
				"scope":                      "openid profile",
				"token_endpoint_auth_method": "private_key_jwt",
				"jwks_uri":                   "https://headless.example.com/jwks.json",
				"metadata": map[string]any{
					"status":                     "active",
					"headless_login_enabled":     true,
					"request_object_signing_alg": "RS256",
				},
			}), nil
		}

		return httpJSONAny(r, http.StatusNotFound, nil), nil
	})

	secretRepo := &mockSecretRepo{secrets: make(map[string]string)}
	redisRepo := &mockRedisRepo{data: make(map[string]string)}

	h := &handler.DevHandler{
		Hydra: &service.HydraAdminService{
			AdminURL:   "http://hydra.test",
			PublicURL:  "http://hydra.public",
			HTTPClient: &http.Client{Transport: transport},
		},
		SecretRepo: secretRepo,
		Redis:      redisRepo,
	}

	app := fiber.New()
	app.Use(func(c *fiber.Ctx) error {
		c.Locals("user_profile", &domain.UserProfileResponse{ID: "test-user", Role: domain.RoleSuperAdmin})
		return c.Next()
	})
	app.Put("/api/v1/dev/clients/:id", h.UpdateClient)
	app.Get("/api/v1/dev/clients/:id", h.GetClient)

	updateBody, _ := json.Marshal(map[string]any{
		"name":                    "Headless Login After",
		"redirectUris":            []string{"https://headless.example.com/callback"},
		"tokenEndpointAuthMethod": "private_key_jwt",
		"jwksUri":                 "https://headless.example.com/jwks.json",
		"metadata": map[string]any{
			"headless_login_enabled":     true,
			"request_object_signing_alg": "RS256",
		},
	})
	updateReq := httptest.NewRequest(http.MethodPut, "/api/v1/dev/clients/client-headless-login", bytes.NewReader(updateBody))
	updateReq.Header.Set("Content-Type", "application/json")

	updateResp, err := app.Test(updateReq, -1)
	if err != nil {
		t.Fatalf("update request failed: %v", err)
	}
	if updateResp.StatusCode != http.StatusOK {
		t.Fatalf("expected update 200, got %d", updateResp.StatusCode)
	}

	storedSecret, _ := secretRepo.GetByID(context.Background(), "client-headless-login")
	if storedSecret != "headless-secret" {
		t.Fatalf("expected postgres secret headless-secret, got %q", storedSecret)
	}

	redisSecret, err := redisRepo.Get("client_secret:client-headless-login")
	if err != nil {
		t.Fatalf("expected redis secret, got error: %v", err)
	}
	if redisSecret != "headless-secret" {
		t.Fatalf("expected redis secret headless-secret, got %q", redisSecret)
	}

	getReq := httptest.NewRequest(http.MethodGet, "/api/v1/dev/clients/client-headless-login", nil)
	getResp, err := app.Test(getReq, -1)
	if err != nil {
		t.Fatalf("get request failed: %v", err)
	}
	if getResp.StatusCode != http.StatusOK {
		t.Fatalf("expected get 200, got %d", getResp.StatusCode)
	}

	var payload struct {
		Client struct {
			ClientSecret string `json:"clientSecret"`
		} `json:"client"`
	}
	if err := json.NewDecoder(getResp.Body).Decode(&payload); err != nil {
		t.Fatalf("decode response: %v", err)
	}
	if payload.Client.ClientSecret != "headless-secret" {
		t.Fatalf("expected detail secret headless-secret, got %q", payload.Client.ClientSecret)
	}
}
