package audit

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

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"
)

type fakeResolver struct{ uid uuid.UUID }

func (f *fakeResolver) ResolveSession(_ context.Context, token string) (uuid.UUID, bool, error) {
	if token == "" {
		return uuid.Nil, false, nil
	}
	return f.uid, true, 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
}

type fakeReader struct {
	lastFilter AuditFilter
	entries    []LogEntry
	total      int
}

func (f *fakeReader) List(_ context.Context, flt AuditFilter) ([]LogEntry, int, error) {
	f.lastFilter = flt
	return f.entries, f.total, nil
}
func (f *fakeReader) PurgeBefore(_ context.Context, _ time.Time) (int64, error) { return 0, nil }

func mount(reader Reader, uid uuid.UUID, admins map[uuid.UUID]bool) http.Handler {
	r := chi.NewRouter()
	NewHandler(reader, &fakeResolver{uid: uid}, &fakeAdmin{admins: admins}).Mount(r)
	return r
}

func TestAuditHandlerNonAdminForbidden(t *testing.T) {
	uid := uuid.New()
	h := mount(&fakeReader{}, uid, map[uuid.UUID]bool{}) // not admin
	req := httptest.NewRequest("GET", "/api/v1/admin/audit-logs/", nil)
	req.Header.Set("Authorization", "Bearer tok")
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusForbidden {
		t.Fatalf("status = %d, want 403", rec.Code)
	}
}

func TestAuditHandlerUnauthenticated(t *testing.T) {
	uid := uuid.New()
	h := mount(&fakeReader{}, uid, map[uuid.UUID]bool{uid: true})
	req := httptest.NewRequest("GET", "/api/v1/admin/audit-logs/", nil) // no token
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusUnauthorized {
		t.Fatalf("status = %d, want 401", rec.Code)
	}
}

func TestAuditHandlerAdminListWithFilters(t *testing.T) {
	uid := uuid.New()
	filterUID := uuid.New()
	now := time.Now().UTC().Truncate(time.Second)
	reader := &fakeReader{
		entries: []LogEntry{{
			ID:        7,
			UserID:    &filterUID,
			Action:    "user.login",
			CreatedAt: now,
		}},
		total: 1,
	}
	h := mount(reader, uid, map[uuid.UUID]bool{uid: true})

	from := now.Add(-time.Hour).Format(time.RFC3339)
	url := "/api/v1/admin/audit-logs/?action=user.login&target_type=user&user_id=" +
		filterUID.String() + "&from=" + from + "&limit=10&offset=5"
	req := httptest.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer tok")
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status = %d, want 200; body=%s", rec.Code, rec.Body.String())
	}
	// Filter parsed and forwarded to reader.
	if reader.lastFilter.Action != "user.login" {
		t.Errorf("action filter = %q, want user.login", reader.lastFilter.Action)
	}
	if reader.lastFilter.TargetType != "user" {
		t.Errorf("target_type filter = %q", reader.lastFilter.TargetType)
	}
	if reader.lastFilter.UserID == nil || *reader.lastFilter.UserID != filterUID {
		t.Errorf("user_id filter not parsed: %v", reader.lastFilter.UserID)
	}
	if reader.lastFilter.From == nil {
		t.Errorf("from filter not parsed")
	}
	if reader.lastFilter.Limit != 10 || reader.lastFilter.Offset != 5 {
		t.Errorf("limit/offset = %d/%d, want 10/5", reader.lastFilter.Limit, reader.lastFilter.Offset)
	}

	var resp struct {
		Items []auditLogDTO `json:"items"`
		Total int           `json:"total"`
	}
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("decode: %v", err)
	}
	if resp.Total != 1 || len(resp.Items) != 1 || resp.Items[0].ID != 7 {
		t.Errorf("unexpected response: %+v", resp)
	}
}

func TestAuditHandlerBadQueryParam(t *testing.T) {
	uid := uuid.New()
	h := mount(&fakeReader{}, uid, map[uuid.UUID]bool{uid: true})
	req := httptest.NewRequest("GET", "/api/v1/admin/audit-logs/?from=not-a-date", nil)
	req.Header.Set("Authorization", "Bearer tok")
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400; body=%s", rec.Code, rec.Body.String())
	}
}
