package security

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

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
	"github.com/stretchr/testify/require"

	"github.com/yan1h/agent-coding-workflow/internal/jobs"
)

type fakeRotator struct {
	active  int
	rotated bool
}

func (f *fakeRotator) RotateActive(_ context.Context, _, _ string) (int, error) {
	f.active++
	f.rotated = true
	return f.active, nil
}
func (f *fakeRotator) ActiveVersion(context.Context) (int, error) { return f.active, nil }

type fakeEnqueuer struct{ enqueued jobs.JobType }

func (f *fakeEnqueuer) Enqueue(_ context.Context, typ jobs.JobType, _ json.RawMessage, _ time.Time, _ int32) (*jobs.Job, error) {
	f.enqueued = typ
	return &jobs.Job{ID: uuid.New(), Type: typ}, nil
}

type fakeStatus struct{ stale map[string]int }

func (f *fakeStatus) Tables() []string { return []string{"workspaces"} }
func (f *fakeStatus) StaleCount(_ context.Context, t string, _ int) (int, error) {
	return f.stale[t], nil
}

// fakeResolver maps a fixed token to a fixed user id.
type fakeResolver struct{ uid uuid.UUID }

func (f fakeResolver) ResolveSession(_ context.Context, token string) (uuid.UUID, bool, error) {
	if token == "good" {
		return f.uid, true, nil
	}
	return uuid.Nil, false, nil
}

type fakeAdmin struct{ admins map[uuid.UUID]bool }

func (f fakeAdmin) IsAdmin(_ context.Context, id uuid.UUID) (bool, error) {
	return f.admins[id], nil
}

func mountHandler(t *testing.T, admin bool) (*chi.Mux, *fakeRotator, *fakeEnqueuer) {
	t.Helper()
	uid := uuid.New()
	rot := &fakeRotator{active: 1}
	enq := &fakeEnqueuer{}
	st := &fakeStatus{stale: map[string]int{"workspaces": 2}}
	h := NewHandler(rot, enq, st, fakeResolver{uid: uid}, fakeAdmin{admins: map[uuid.UUID]bool{uid: admin}}, nil)
	r := chi.NewRouter()
	h.Mount(r)
	return r, rot, enq
}

func TestRotateKey_AdminEnqueuesReencrypt(t *testing.T) {
	r, rot, enq := mountHandler(t, true)
	req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/security/rotate-key", nil)
	req.Header.Set("Authorization", "Bearer good")
	rr := httptest.NewRecorder()
	r.ServeHTTP(rr, req)

	require.Equal(t, http.StatusAccepted, rr.Code)
	require.True(t, rot.rotated)
	require.Equal(t, jobs.TypeSecretReencrypt, enq.enqueued)

	var resp rotateKeyResp
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
	require.Equal(t, 2, resp.ActiveVersion)
	require.NotEmpty(t, resp.JobID)
}

func TestRotateKey_NonAdminForbidden(t *testing.T) {
	r, rot, enq := mountHandler(t, false)
	req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/security/rotate-key", nil)
	req.Header.Set("Authorization", "Bearer good")
	rr := httptest.NewRecorder()
	r.ServeHTTP(rr, req)

	require.Equal(t, http.StatusForbidden, rr.Code)
	require.False(t, rot.rotated)
	require.Empty(t, enq.enqueued)
}

func TestRotateKey_Unauthenticated(t *testing.T) {
	r, _, _ := mountHandler(t, true)
	req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/security/rotate-key", nil)
	req.Header.Set("Authorization", "Bearer bad")
	rr := httptest.NewRecorder()
	r.ServeHTTP(rr, req)
	require.Equal(t, http.StatusUnauthorized, rr.Code)
}

func TestReencryptStatus_ReportsStale(t *testing.T) {
	r, _, _ := mountHandler(t, true)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/admin/security/reencrypt-status", nil)
	req.Header.Set("Authorization", "Bearer good")
	rr := httptest.NewRecorder()
	r.ServeHTTP(rr, req)

	require.Equal(t, http.StatusOK, rr.Code)
	var resp reencryptStatusResp
	require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp))
	require.Equal(t, 2, resp.Stale["workspaces"])
	require.False(t, resp.Complete)
}
