package handlers

import (
	"context"
	"testing"
	"time"

	"armature/models"
	"armature/store"
)

// mockGlobalConfig implements store.GlobalConfigStore for tests.
type mockGlobalConfig struct {
	data map[string]models.JSONMap
}

func (m *mockGlobalConfig) Get(_ context.Context, key string) (models.JSONMap, error) {
	if v, ok := m.data[key]; ok {
		return v, nil
	}
	return nil, nil
}
func (m *mockGlobalConfig) Set(_ context.Context, _ string, _ models.JSONMap, _ string) error {
	return nil
}
func (m *mockGlobalConfig) GetAll(_ context.Context) (map[string]models.JSONMap, error) {
	return m.data, nil
}
func (m *mockGlobalConfig) SaveOIDCState(_ context.Context, _, _, _ string) error { return nil }
func (m *mockGlobalConfig) ConsumeOIDCState(_ context.Context, _ string) (string, string, error) {
	return "", "", nil
}
func (m *mockGlobalConfig) CleanupOIDCState(_ context.Context) error { return nil }
func (m *mockGlobalConfig) GetString(_ context.Context, _ string) (string, error) { return "", nil }

func newMockGC(session models.JSONMap) store.GlobalConfigStore {
	return &mockGlobalConfig{data: map[string]models.JSONMap{"session": session}}
}

// ── parseDurationString tests ──────────────

func TestParseDurationString(t *testing.T) {
	tests := []struct {
		input string
		want  time.Duration
		err   bool
	}{
		{"15m", 15 * time.Minute, false},
		{"2h", 2 * time.Hour, false},
		{"7d", 7 * 24 * time.Hour, false},
		{"90d", 90 * 24 * time.Hour, false},
		{"30m", 30 * time.Minute, false},
		{"1h30m", 90 * time.Minute, false},
		{"", 0, true},
		{"abc", 0, true},
		{"d", 0, true},
	}

	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			got, err := parseDurationString(tt.input)
			if tt.err {
				if err == nil {
					t.Errorf("expected error for %q, got %v", tt.input, got)
				}
				return
			}
			if err != nil {
				t.Fatalf("unexpected error for %q: %v", tt.input, err)
			}
			if got != tt.want {
				t.Errorf("parseDurationString(%q) = %v, want %v", tt.input, got, tt.want)
			}
		})
	}
}

// ── LoadSessionConfig tests ────────────────

func TestSessionConfigDefaults(t *testing.T) {
	gc := &mockGlobalConfig{data: map[string]models.JSONMap{}}
	sc := LoadSessionConfig(context.Background(), gc)

	if sc.AccessTokenTTL != 15*time.Minute {
		t.Errorf("AccessTokenTTL = %v, want 15m", sc.AccessTokenTTL)
	}
	if sc.RefreshTokenTTL != 7*24*time.Hour {
		t.Errorf("RefreshTokenTTL = %v, want 7d", sc.RefreshTokenTTL)
	}
	if sc.IdleTimeout != 0 {
		t.Errorf("IdleTimeout = %v, want 0", sc.IdleTimeout)
	}
}

func TestSessionConfigCustomValues(t *testing.T) {
	gc := newMockGC(models.JSONMap{
		"access_token_ttl":  "30m",
		"refresh_token_ttl": "30d",
		"idle_timeout":      "1h",
	})

	sc := LoadSessionConfig(context.Background(), gc)

	if sc.AccessTokenTTL != 30*time.Minute {
		t.Errorf("AccessTokenTTL = %v, want 30m", sc.AccessTokenTTL)
	}
	if sc.RefreshTokenTTL != 30*24*time.Hour {
		t.Errorf("RefreshTokenTTL = %v, want 30d", sc.RefreshTokenTTL)
	}
	if sc.IdleTimeout != 1*time.Hour {
		t.Errorf("IdleTimeout = %v, want 1h", sc.IdleTimeout)
	}
}

func TestSessionConfigClampLow(t *testing.T) {
	gc := newMockGC(models.JSONMap{
		"access_token_ttl":  "1m",   // below 5m min
		"refresh_token_ttl": "30m",  // below 1h min
		"idle_timeout":      "1m",   // below 5m min
	})

	sc := LoadSessionConfig(context.Background(), gc)

	if sc.AccessTokenTTL != 5*time.Minute {
		t.Errorf("AccessTokenTTL clamped = %v, want 5m", sc.AccessTokenTTL)
	}
	if sc.RefreshTokenTTL != 1*time.Hour {
		t.Errorf("RefreshTokenTTL clamped = %v, want 1h", sc.RefreshTokenTTL)
	}
	if sc.IdleTimeout != 5*time.Minute {
		t.Errorf("IdleTimeout clamped = %v, want 5m", sc.IdleTimeout)
	}
}

func TestSessionConfigClampHigh(t *testing.T) {
	gc := newMockGC(models.JSONMap{
		"access_token_ttl":  "2h",   // above 60m max
		"refresh_token_ttl": "365d", // above 90d max
		"idle_timeout":      "48h",  // above 24h max
	})

	sc := LoadSessionConfig(context.Background(), gc)

	if sc.AccessTokenTTL != 60*time.Minute {
		t.Errorf("AccessTokenTTL clamped = %v, want 60m", sc.AccessTokenTTL)
	}
	if sc.RefreshTokenTTL != 90*24*time.Hour {
		t.Errorf("RefreshTokenTTL clamped = %v, want 90d", sc.RefreshTokenTTL)
	}
	if sc.IdleTimeout != 24*time.Hour {
		t.Errorf("IdleTimeout clamped = %v, want 24h", sc.IdleTimeout)
	}
}

func TestSessionConfigEmptyIdleTimeout(t *testing.T) {
	gc := newMockGC(models.JSONMap{
		"access_token_ttl":  "15m",
		"refresh_token_ttl": "7d",
		"idle_timeout":      "",
	})

	sc := LoadSessionConfig(context.Background(), gc)

	if sc.IdleTimeout != 0 {
		t.Errorf("IdleTimeout = %v, want 0 (disabled)", sc.IdleTimeout)
	}
}

func TestSessionConfigInvalidValues(t *testing.T) {
	gc := newMockGC(models.JSONMap{
		"access_token_ttl":  "not-a-duration",
		"refresh_token_ttl": "xyz",
		"idle_timeout":      "---",
	})

	sc := LoadSessionConfig(context.Background(), gc)

	// Should fall back to defaults
	if sc.AccessTokenTTL != 15*time.Minute {
		t.Errorf("AccessTokenTTL = %v, want 15m (default)", sc.AccessTokenTTL)
	}
	if sc.RefreshTokenTTL != 7*24*time.Hour {
		t.Errorf("RefreshTokenTTL = %v, want 7d (default)", sc.RefreshTokenTTL)
	}
	if sc.IdleTimeout != 0 {
		t.Errorf("IdleTimeout = %v, want 0 (default)", sc.IdleTimeout)
	}
}
