package webui

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

	"mailgoserver/internal/db"
	"mailgoserver/internal/mailstore"
)

// createMailboxFor mirrors setupTwoTenants' sender helper, for a mailbox instead.
func createMailboxFor(t *testing.T, app *App, email string, domainID int64) *db.Mailbox {
	t.Helper()
	hash, err := db.HashPassword("password123")
	if err != nil {
		t.Fatal(err)
	}
	dek := mailstore.GenerateDEK()
	wrapped, nonce, err := app.Mailstore.WrapDEK(dek)
	if err != nil {
		t.Fatal(err)
	}
	id, err := app.DB.CreateMailbox(email, hash, domainID, 5*1024*1024*1024, wrapped, nonce)
	if err != nil {
		t.Fatal(err)
	}
	mbox, err := app.DB.GetMailboxByID(id)
	if err != nil || mbox == nil {
		t.Fatalf("expected mailbox to exist: %v", err)
	}
	return mbox
}

func TestScopedAdminCannotAccessOtherTenantMailboxByID(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domainA, domainB, _, _ := setupTwoTenants(t, app)
	mailboxB := createMailboxFor(t, app, "carol@"+domainB.DomainName, domainB.ID)

	cookie := scopedLogin(t, app, "tenant-a-admin", []int64{domainA.ID})

	req := httptest.NewRequest(http.MethodGet, Prefix+"/mailboxes/"+strconv.FormatInt(mailboxB.ID, 10)+"/edit", nil)
	req.AddCookie(cookie)
	rec := httptest.NewRecorder()
	mux.ServeHTTP(rec, req)
	if rec.Code != http.StatusNotFound {
		t.Fatalf("expected 404 for a mailbox outside scope, got %d", rec.Code)
	}

	form := url.Values{}
	req2 := httptest.NewRequest(http.MethodPost, Prefix+"/mailboxes/"+strconv.FormatInt(mailboxB.ID, 10)+"/delete", strings.NewReader(form.Encode()))
	req2.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req2.AddCookie(cookie)
	rec2 := httptest.NewRecorder()
	mux.ServeHTTP(rec2, req2)
	if rec2.Code != http.StatusNotFound {
		t.Fatalf("expected 404 disabling a mailbox outside scope, got %d", rec2.Code)
	}
	stillActive, err := app.DB.GetMailboxByID(mailboxB.ID)
	if err != nil || stillActive == nil || !stillActive.IsActive {
		t.Fatal("mailbox outside scope must not have been modified")
	}
}

func TestScopedAdminCannotCreateMailboxOnUnownedDomain(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domainA, domainB, _, _ := setupTwoTenants(t, app)

	cookie := scopedLogin(t, app, "tenant-a-admin", []int64{domainA.ID})

	form := url.Values{
		"local_part": {"mallory"},
		"domain_id":  {strconv.FormatInt(domainB.ID, 10)}, // not theirs
		"password":   {"password123"},
	}
	req := httptest.NewRequest(http.MethodPost, Prefix+"/mailboxes/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.StatusNotFound {
		t.Fatalf("expected 404 creating a mailbox on an unowned domain, got %d", rec.Code)
	}
	if m, _ := app.DB.GetMailboxByEmail("mallory@" + domainB.DomainName); m != nil {
		t.Fatal("mailbox must not have been created on a domain outside the admin's scope")
	}
}

func TestScopedAdminCanManageOwnMailboxAppPasswords(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domainA, _, _, _ := setupTwoTenants(t, app)
	mailboxA := createMailboxFor(t, app, "dave@"+domainA.DomainName, domainA.ID)

	cookie := scopedLogin(t, app, "tenant-a-admin", []int64{domainA.ID})

	form := url.Values{"label": {"laptop"}}
	req := httptest.NewRequest(http.MethodPost, Prefix+"/mailboxes/"+strconv.FormatInt(mailboxA.ID, 10)+"/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("expected redirect after creating an app password, got %d: %s", rec.Code, rec.Body.String())
	}

	passwords, err := app.DB.ListAppPasswordsForMailbox(mailboxA.ID)
	if err != nil || len(passwords) != 1 {
		t.Fatalf("expected exactly one app password, got %d (err=%v)", len(passwords), err)
	}
	if len(passwords[0].PasswordHash) == 0 {
		t.Fatal("expected a password hash to be stored")
	}
}

func TestScopedAdminCannotCreateAliasOnUnownedDomain(t *testing.T) {
	app := newTestApp(t)
	mux := app.Mux()
	domainA, domainB, _, _ := setupTwoTenants(t, app)
	mailboxA := createMailboxFor(t, app, "dave@"+domainA.DomainName, domainA.ID)

	cookie := scopedLogin(t, app, "tenant-a-admin", []int64{domainA.ID})

	form := url.Values{
		"local_part": {"evilalias"},
		"domain_id":  {strconv.FormatInt(domainB.ID, 10)}, // not theirs
	}
	req := httptest.NewRequest(http.MethodPost, Prefix+"/mailboxes/"+strconv.FormatInt(mailboxA.ID, 10)+"/aliases/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.StatusNotFound {
		t.Fatalf("expected 404 creating an alias on an unowned domain, got %d", rec.Code)
	}
	if a, _ := app.DB.GetAliasByEmail("evilalias@" + domainB.DomainName); a != nil {
		t.Fatal("alias must not have been created on a domain outside the admin's scope")
	}
}

func TestAppPasswordCannotBeRevokedFromAnotherMailbox(t *testing.T) {
	app := newTestApp(t)
	domainA, domainB, _, _ := setupTwoTenants(t, app)
	mailboxA := createMailboxFor(t, app, "dave@"+domainA.DomainName, domainA.ID)
	mailboxB := createMailboxFor(t, app, "carol@"+domainB.DomainName, domainB.ID)

	pwID, err := app.DB.CreateAppPassword(mailboxB.ID, "carol's laptop", "irrelevant-hash", nil)
	if err != nil {
		t.Fatal(err)
	}

	// Attempting to remove mailboxB's app password while scoped to mailboxA must be a
	// no-op — RemoveAppPassword is scoped by (id, mailboxID) precisely to prevent this.
	if err := app.DB.RemoveAppPassword(pwID, mailboxA.ID); err != nil {
		t.Fatal(err)
	}
	remaining, err := app.DB.ListAppPasswordsForMailbox(mailboxB.ID)
	if err != nil || len(remaining) != 1 {
		t.Fatalf("expected mailboxB's app password to survive a delete scoped to mailboxA, got %d remaining (err=%v)", len(remaining), err)
	}
}
