package auth

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"

	"gitlab.com/dynaweb/ains-microservices/pkg/config"
	"gitlab.com/dynaweb/ains-microservices/pkg/delima"
	"gitlab.com/dynaweb/ains-microservices/pkg/jwt"
	"gitlab.com/dynaweb/ains-microservices/pkg/oauth2"
)

func newTestHandler() (*gin.Engine, *Handler) {
	cfg := &config.Config{}
	cfg.JWT.Secret = "test-secret"
	cfg.JWT.Issuer = "ains-test"
	cfg.JWT.AccessTTL = 60000000000
	cfg.JWT.RefreshTTL = 3600000000000
	cfg.Auth.CookieName = "ains_token"
	cfg.Google.Mock = true

	store := NewMemoryStore()
	svc := NewService(store, store, delima.New("", ""), jwt.NewManager(cfg), NewMemoryDenylist(), "/web/home")
	handler := NewHandler(svc, oauth2.NewGoogle("", "", ""), cfg)

	gin.SetMode(gin.TestMode)
	r := gin.New()
	handler.Register(r)
	return r, handler
}

func cookieFrom(t *testing.T, res *http.Response, name string) *http.Cookie {
	t.Helper()
	for _, c := range res.Cookies() {
		if c.Name == name {
			return c
		}
	}
	t.Fatalf("cookie %q not found", name)
	return nil
}

func TestLoginFlowMock(t *testing.T) {
	r, _ := newTestHandler()

	login := httptest.NewRecorder()
	r.ServeHTTP(login, httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/auth/login?email=m-100@moe-dl.edu.my&name=Aminah", nil))

	loginRes := login.Result()
	if loginRes.StatusCode != http.StatusFound {
		t.Fatalf("login status = %d", loginRes.StatusCode)
	}
	state := cookieFrom(t, loginRes, stateCookieName).Value
	loc, err := url.Parse(loginRes.Header.Get("Location"))
	if err != nil {
		t.Fatal(err)
	}
	if loc.Path != "/auth/google/callback" || loc.Query().Get("state") != state {
		t.Fatalf("unexpected redirect %q", loc)
	}

	cb := httptest.NewRequestWithContext(context.Background(), http.MethodGet, loc.String(), nil)
	cb.AddCookie(&http.Cookie{Name: stateCookieName, Value: state})
	cbRec := httptest.NewRecorder()
	r.ServeHTTP(cbRec, cb)

	cbRes := cbRec.Result()
	if cbRes.StatusCode != http.StatusFound {
		t.Fatalf("callback status = %d", cbRes.StatusCode)
	}
	if got := cbRes.Header.Get("Location"); got != "/web/murid-dashboard" {
		t.Fatalf("callback redirect = %q", got)
	}
	access := cookieFrom(t, cbRes, "ains_token")
	if access.Value == "" {
		t.Fatal("expected access cookie")
	}

	me := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/auth/me", nil)
	me.AddCookie(&http.Cookie{Name: "ains_token", Value: access.Value})
	meRec := httptest.NewRecorder()
	r.ServeHTTP(meRec, me)

	if meRec.Code != http.StatusOK {
		t.Fatalf("me status = %d", meRec.Code)
	}
	var body map[string]any
	if err := json.Unmarshal(meRec.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	if body["role"] != RoleMurid {
		t.Fatalf("me role = %v", body["role"])
	}

	logout := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/auth/logout", nil)
	logout.AddCookie(&http.Cookie{Name: "ains_token", Value: access.Value})
	logoutRec := httptest.NewRecorder()
	r.ServeHTTP(logoutRec, logout)
	if logoutRec.Code != http.StatusOK {
		t.Fatalf("logout status = %d", logoutRec.Code)
	}

	meAfter := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/auth/me", nil)
	meAfter.AddCookie(&http.Cookie{Name: "ains_token", Value: access.Value})
	meAfterRec := httptest.NewRecorder()
	r.ServeHTTP(meAfterRec, meAfter)
	if meAfterRec.Code != http.StatusUnauthorized {
		t.Fatalf("me after logout = %d, want 401", meAfterRec.Code)
	}
}

func TestCallbackRejectsBadState(t *testing.T) {
	r, _ := newTestHandler()
	req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/auth/google/callback?state=nope&email=m-1@moe-dl.edu.my", nil)
	req.AddCookie(&http.Cookie{Name: stateCookieName, Value: "different"})
	rec := httptest.NewRecorder()
	r.ServeHTTP(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400", rec.Code)
	}
}

func TestCallbackUnknownRoleRedirectsFailed(t *testing.T) {
	r, _ := newTestHandler()
	login := httptest.NewRecorder()
	r.ServeHTTP(login, httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/auth/login?email=alien-1@example.com", nil))

	state := cookieFrom(t, login.Result(), stateCookieName).Value
	req := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/auth/google/callback?state="+state+"&email=alien-1@example.com", nil)
	req.AddCookie(&http.Cookie{Name: stateCookieName, Value: state})
	rec := httptest.NewRecorder()
	r.ServeHTTP(rec, req)

	if rec.Code != http.StatusFound {
		t.Fatalf("status = %d", rec.Code)
	}
	if !strings.HasSuffix(rec.Header().Get("Location"), "/web/auth-failed") {
		t.Fatalf("redirect = %q", rec.Header().Get("Location"))
	}
}
