package webui

import (
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"
	"time"

	"mailgoserver/internal/db"
)

func TestAppPasswordExpiryPresets(t *testing.T) {
	loc := time.UTC
	now := time.Now()

	t.Run("never expires by default", func(t *testing.T) {
		got, err := appPasswordExpiry("", "", loc)
		if err != nil || got != nil {
			t.Fatalf("got (%v, %v), want (nil, nil)", got, err)
		}
	})

	t.Run("7 day preset", func(t *testing.T) {
		got, err := appPasswordExpiry("7d", "", loc)
		if err != nil || got == nil {
			t.Fatalf("got (%v, %v), want a non-nil expiry", got, err)
		}
		wantAround := now.Add(7 * 24 * time.Hour)
		if diff := got.Sub(wantAround); diff < -time.Minute || diff > time.Minute {
			t.Fatalf("expiry %v not within a minute of %v", got, wantAround)
		}
	})

	t.Run("custom date is end of day", func(t *testing.T) {
		got, err := appPasswordExpiry("custom", "2030-01-15", loc)
		if err != nil || got == nil {
			t.Fatalf("got (%v, %v), want a non-nil expiry", got, err)
		}
		want := time.Date(2030, 1, 15, 23, 59, 59, 0, loc)
		if !got.Equal(want) {
			t.Fatalf("got %v, want %v", got, want)
		}
	})

	t.Run("custom without a date errors", func(t *testing.T) {
		if _, err := appPasswordExpiry("custom", "", loc); err == nil {
			t.Fatal("expected an error for a missing custom date")
		}
	})

	t.Run("invalid preset errors", func(t *testing.T) {
		if _, err := appPasswordExpiry("bogus", "", loc); err == nil {
			t.Fatal("expected an error for an invalid preset")
		}
	})
}

// TestAddAppPasswordExpiredIsRejectedByAuth confirms an app password created with a
// past expiry (simulated directly at the DB layer, since the UI can only pick future
// dates) can no longer authenticate, even while still marked active.
func TestAddAppPasswordExpiredIsRejectedByAuth(t *testing.T) {
	app := newTestApp(t)
	mailboxes, _ := app.DB.ListMailboxes()
	if len(mailboxes) == 0 {
		t.Fatal("no seeded mailbox")
	}
	mbox := mailboxes[0].Mailbox

	past := time.Now().Add(-time.Hour)
	hash, err := db.HashPassword("some-secret-app-password-value")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := app.DB.CreateAppPassword(mbox.ID, "expired", hash, &past); err != nil {
		t.Fatal(err)
	}

	got, err := app.DB.VerifyMailboxAppPassword(mbox.Email, "some-secret-app-password-value")
	if err != nil {
		t.Fatal(err)
	}
	if got != nil {
		t.Fatal("expired app password must not authenticate")
	}
}

// TestAddAppPasswordSetsRevealCookieNotFlash confirms the create handler no longer
// puts the plaintext secret in the toast-driven Flash cookie (easy to miss, no copy
// button) and instead sets the dedicated one-time reveal cookie the list page renders
// as a modal.
func TestAddAppPasswordSetsRevealCookieNotFlash(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	cookie := loginSession(t, app)
	mailboxes, _ := app.DB.ListMailboxes()
	mboxID := mailboxes[0].ID

	form := url.Values{"label": {"laptop"}, "expires_preset": {"never"}}
	req := httptest.NewRequest(http.MethodPost, "/pymta-manager/mailboxes/"+itoa(mboxID)+"/apppasswords/add", strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)

	if rec.Code != http.StatusFound {
		t.Fatalf("add app password: status=%d body=%s", rec.Code, rec.Body.String())
	}
	var revealCookie, flashCookie *http.Cookie
	for _, c := range rec.Result().Cookies() {
		switch c.Name {
		case appPasswordRevealCookieName:
			revealCookie = c
		case flashCookieName:
			flashCookie = c
		}
	}
	if revealCookie == nil || revealCookie.Value == "" {
		t.Fatal("expected a non-empty app password reveal cookie")
	}
	if flashCookie != nil && flashCookie.Value != "" {
		t.Fatalf("flash cookie should not carry the secret, got %q", flashCookie.Value)
	}

	// Following the redirect (as the browser would) should render the reveal modal
	// with the secret, and clear the one-time cookie.
	req2 := httptest.NewRequest(http.MethodGet, "/pymta-manager/mailboxes/"+itoa(mboxID)+"/apppasswords", nil)
	req2.AddCookie(cookie)
	req2.AddCookie(revealCookie)
	rec2 := httptest.NewRecorder()
	mux.ServeHTTP(rec2, req2)
	if rec2.Code != http.StatusOK {
		t.Fatalf("apppasswords list: status=%d", rec2.Code)
	}
	if !strings.Contains(rec2.Body.String(), "appPasswordRevealModal") {
		t.Fatal("expected the reveal modal markup in the response")
	}
}
