package api

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"

	"github.com/tidbcloud/aisupport/services/oncall/db"
	"github.com/tidbcloud/aisupport/services/oncall/models"
	"github.com/tidbcloud/aisupport/services/oncall/service"

	"github.com/gin-gonic/gin"
)

// TestEditorPermission_EndToEnd walks the editor role through the HTTP surface:
// it may change the schedule and the plain-member roster, but every route that
// hands out privileges or retires the team stays manager-only.
func TestEditorPermission_EndToEnd(t *testing.T) {
	setupOverlayPermissionDB(t)
	t.Setenv("ALLOWED_EMAIL_DOMAINS", "pingcap.com")
	t.Setenv("FEISHU_APP_ID", "")
	t.Setenv("FEISHU_APP_SECRET", "")

	const managerEmail = "editor_test_manager@pingcap.com"
	const editorEmail = "editor_test_editor@pingcap.com"
	const memberEmail = "editor_test_member@pingcap.com"

	team := models.Team{Name: "team_editor", DisplayName: "Team Editor", GroupNames: models.StringList{"ONCALL"}}
	db.DB.Create(&team)
	engineerIDs := map[string]uint{}
	for email, role := range map[string]string{
		managerEmail: models.TeamRoleManager,
		editorEmail:  models.TeamRoleEditor,
		memberEmail:  models.TeamRoleMember,
	} {
		eng := models.Engineer{Name: email, Email: email}
		db.DB.Create(&eng)
		db.DB.Create(&models.EngineerTeam{EngineerID: eng.ID, TeamID: team.ID, Role: role})
		engineerIDs[email] = eng.ID
	}

	r := gin.New()
	r.Use(func(c *gin.Context) {
		if email := c.GetHeader("X-Test-User"); email != "" {
			c.Set("request_user_email", email)
		}
		c.Next()
	})
	RegisterRoutes(r)

	do := func(method, url, asUser string, body interface{}) *httptest.ResponseRecorder {
		var rdr *bytes.Reader
		if body != nil {
			b, _ := json.Marshal(body)
			rdr = bytes.NewReader(b)
		} else {
			rdr = bytes.NewReader(nil)
		}
		req := httptest.NewRequest(method, url, rdr)
		req.Header.Set("Content-Type", "application/json")
		if asUser != "" {
			req.Header.Set("X-Test-User", asUser)
		}
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)
		return w
	}

	teamURL := "/api/v1/teams/" + strconv.Itoa(int(team.ID))

	// --- What an editor is for: schedule and roster writes ---

	if w := do(http.MethodPut, teamURL+"/backup-lane", editorEmail, map[string]interface{}{"enabled": true}); w.Code != http.StatusOK {
		t.Fatalf("editor toggling backup lane: expected 200, got %d %s", w.Code, w.Body.String())
	}
	if w := do(http.MethodPatch, teamURL, editorEmail, map[string]interface{}{"description": "owned by the editor"}); w.Code != http.StatusOK {
		t.Fatalf("editor updating description: expected 200, got %d %s", w.Code, w.Body.String())
	}
	if w := do(http.MethodDelete, teamURL+"/engineers/"+memberEmail, editorEmail, nil); w.Code != http.StatusOK {
		t.Fatalf("editor removing a plain member: expected 200, got %d %s", w.Code, w.Body.String())
	}

	// A plain member still has no write access at all.
	if w := do(http.MethodPut, teamURL+"/backup-lane", memberEmail, map[string]interface{}{"enabled": false}); w.Code != http.StatusForbidden {
		t.Fatalf("member toggling backup lane: expected 403, got %d %s", w.Code, w.Body.String())
	}

	// --- Where the editor stops: anything that grants privileges ---

	// Directly, via the role and manager routes.
	if w := do(http.MethodPut, teamURL+"/engineers/"+editorEmail+"/role", editorEmail, map[string]interface{}{"role": "manager"}); w.Code != http.StatusForbidden {
		t.Fatalf("editor granting themselves manager: expected 403, got %d %s", w.Code, w.Body.String())
	}
	if w := do(http.MethodPost, teamURL+"/managers", editorEmail, map[string]interface{}{"engineer_email": editorEmail}); w.Code != http.StatusForbidden {
		t.Fatalf("editor adding a manager: expected 403, got %d %s", w.Code, w.Body.String())
	}
	if w := do(http.MethodDelete, teamURL+"/managers/"+managerEmail, editorEmail, nil); w.Code != http.StatusForbidden {
		t.Fatalf("editor revoking a manager: expected 403, got %d %s", w.Code, w.Body.String())
	}

	// Indirectly, by smuggling a managers list through the team PATCH...
	if w := do(http.MethodPatch, teamURL, editorEmail, map[string]interface{}{"managers": []string{editorEmail}}); w.Code != http.StatusForbidden {
		t.Fatalf("editor rewriting managers via PATCH: expected 403, got %d %s", w.Code, w.Body.String())
	}
	// ...or by deleting a privileged member's membership outright.
	if w := do(http.MethodDelete, teamURL+"/engineers/"+managerEmail, editorEmail, nil); w.Code != http.StatusForbidden {
		t.Fatalf("editor removing a manager as a member: expected 403, got %d %s", w.Code, w.Body.String())
	}
	if w := do(http.MethodDelete, teamURL, editorEmail, nil); w.Code != http.StatusForbidden {
		t.Fatalf("editor disabling the team: expected 403, got %d %s", w.Code, w.Body.String())
	}

	role, err := service.NewScheduleService().TeamRoleOf(team.ID, editorEmail)
	if err != nil {
		t.Fatalf("read back editor role: %v", err)
	}
	if role != models.TeamRoleEditor {
		t.Fatalf("editor role after the escalation attempts = %q, want editor", role)
	}

	// --- A manager can do all of it, including handing out the editor role ---

	if w := do(http.MethodPut, teamURL+"/engineers/"+strconv.Itoa(int(engineerIDs[memberEmail]))+"/role", managerEmail, map[string]interface{}{"role": "editor"}); w.Code != http.StatusOK {
		t.Fatalf("manager granting editor: expected 200, got %d %s", w.Code, w.Body.String())
	}
	if w := do(http.MethodPut, teamURL+"/engineers/"+strconv.Itoa(int(engineerIDs[memberEmail]))+"/role", managerEmail, map[string]interface{}{"role": "owner"}); w.Code != http.StatusBadRequest {
		t.Fatalf("manager granting an unknown role: expected 400, got %d %s", w.Code, w.Body.String())
	}
	// The team's last manager cannot be demoted out of existence.
	if w := do(http.MethodDelete, teamURL+"/managers/"+managerEmail, managerEmail, nil); w.Code != http.StatusBadRequest {
		t.Fatalf("revoking the last manager: expected 400, got %d %s", w.Code, w.Body.String())
	}
	if w := do(http.MethodDelete, teamURL, managerEmail, nil); w.Code != http.StatusOK {
		t.Fatalf("manager disabling the team: expected 200, got %d %s", w.Code, w.Body.String())
	}
}
