package web_test

import (
	"context"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"

	"github.com/stretchr/testify/require"

	"git.d-ma.be/mathias/tapir/internal/adapters/store"
	"git.d-ma.be/mathias/tapir/internal/web"
)

// fakeSecrets is a SecretRemover that records the refs it was asked to delete, so
// account tests can assert the OAuth token cleanup without a real secret file.
type fakeSecrets struct {
	mu      sync.Mutex
	deleted []string
}

func (f *fakeSecrets) Delete(ref string) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.deleted = append(f.deleted, ref)
	return nil
}

func (f *fakeSecrets) deletedRefs() []string {
	f.mu.Lock()
	defer f.mu.Unlock()
	return append([]string(nil), f.deleted...)
}

// newAccountApp builds the App as the registered stub user with a recording fake
// SecretStore, so disconnect/delete can be exercised end-to-end.
func newAccountApp(t *testing.T) (*web.App, *fakeSecrets) {
	t.Helper()
	s := newStore(t)
	fs := &fakeSecrets{}
	return &web.App{
		Store:    s,
		Identity: s,
		Auth:     web.StubAuth{U: web.User{Subject: stubSubject, Email: "ada@example.com"}},
		Secrets:  fs,
	}, fs
}

func seedConnection(t *testing.T, app *web.App, provider, account, ref string) {
	t.Helper()
	require.NoError(t, app.Store.(*store.Store).UpsertConnection(context.Background(), userID, store.Connection{
		Provider:        provider,
		ProviderAccount: account,
		TokenRef:        ref,
		Status:          "active",
	}))
}

func TestAccountPageShowsConnectionAndName(t *testing.T) {
	ctx := context.Background()
	app, _ := newAccountApp(t)
	p := rawPool(t)
	resetDB(t, p)
	_, err := p.Exec(ctx, `UPDATE users SET display_name = $1 WHERE id = $2`, "Ada", userID)
	require.NoError(t, err)
	seedConnection(t, app, "youtube", "ada@channel", web.YouTubeTokenRef(userID))

	rec := do(t, app, httptest.NewRequest(http.MethodGet, "/account", nil))
	require.Equal(t, http.StatusOK, rec.Code)
	html := body(t, rec)

	require.Contains(t, html, "Ada", "display name shown")
	require.Contains(t, html, "ada@example.com", "signed-in email shown")
	require.Contains(t, html, "ada@channel", "connected account shown")
	require.Contains(t, html, "YouTube")
	require.Contains(t, html, "/account/disconnect/youtube", "a Disconnect control is present")
	require.NotContains(t, html, "/oauth/youtube/connect", "no Connect link while already connected")
	// Confirm-before-destroy: the delete is behind a disclosure, not a bare button.
	require.Contains(t, html, "/account/delete")
	require.Contains(t, html, "<details", "delete is gated behind a confirm step")
	require.Contains(t, html, "cannot be undone")
}

func TestAccountPageShowsConnectLinkWhenNotConnected(t *testing.T) {
	app, _ := newAccountApp(t)
	resetDB(t, rawPool(t))

	rec := do(t, app, httptest.NewRequest(http.MethodGet, "/account", nil))
	require.Equal(t, http.StatusOK, rec.Code)
	html := body(t, rec)

	require.Contains(t, html, "/oauth/youtube/connect", "Connect link offered when not connected")
	require.Contains(t, html, "Connect YouTube")
}

func TestDisconnectRemovesTokenAndConnectionKeepsAccount(t *testing.T) {
	ctx := context.Background()
	app, fs := newAccountApp(t)
	resetDB(t, rawPool(t))
	ref := web.YouTubeTokenRef(userID)
	seedConnection(t, app, "youtube", "ada@channel", ref)

	req := httptest.NewRequest(http.MethodPost, "/account/disconnect/youtube", nil)
	rec := do(t, app, req)
	require.Equal(t, http.StatusSeeOther, rec.Code)
	require.Equal(t, "/account", rec.Header().Get("Location"))

	// Token purged from the SecretStore.
	require.Contains(t, fs.deletedRefs(), ref, "the per-user YouTube token must be deleted")

	// Connection row gone...
	conns, err := app.Store.(*store.Store).ConnectionsForUser(ctx, userID)
	require.NoError(t, err)
	require.Empty(t, conns, "the connection row must be removed")

	// ...but the account itself survives (disconnect is not delete).
	name, err := app.Store.(*store.Store).DisplayName(ctx, userID)
	require.NoError(t, err)
	_ = name
	var users int
	require.NoError(t, rawPool(t).QueryRow(ctx, `SELECT count(*) FROM users WHERE id = $1`, userID).Scan(&users))
	require.Equal(t, 1, users, "disconnect must not delete the account")
}

func TestDeleteAccountWipesDataAndSecretsAndLogsOut(t *testing.T) {
	ctx := context.Background()
	app, fs := newAccountApp(t)
	p := rawPool(t)
	resetDB(t, p)
	ref := web.YouTubeTokenRef(userID)
	seedConnection(t, app, "youtube", "ada@channel", ref)
	require.NoError(t, deliver(ctx, app, videoX, "a summary")) // some user data to wipe

	rec := do(t, app, httptest.NewRequest(http.MethodPost, "/account/delete", nil))
	require.Equal(t, http.StatusSeeOther, rec.Code)
	require.Equal(t, "/auth/logout", rec.Header().Get("Location"), "delete logs the user out")

	// The user's secrets were removed (the per-user YouTube token).
	require.Contains(t, fs.deletedRefs(), ref, "delete must purge the user's OAuth tokens")

	// The account and its data are gone.
	for _, q := range []string{
		`SELECT count(*) FROM users WHERE id = $1`,
		`SELECT count(*) FROM summaries WHERE user_id = $1`,
		`SELECT count(*) FROM video_connections WHERE user_id = $1`,
		`SELECT count(*) FROM user_identities WHERE user_id = $1`,
	} {
		var n int
		require.NoError(t, p.QueryRow(ctx, q, userID).Scan(&n))
		require.Equal(t, 0, n, "delete must remove all rows: %s", q)
	}
}
