package api

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/mish/dealspace/lib"
)

// testServerSetup creates a test server with all dependencies
func testServerSetup(t *testing.T) (*httptest.Server, *lib.DB, *lib.Config, func()) {
	t.Helper()

	tmpFile, err := os.CreateTemp("", "dealspace-org-test-*.db")
	if err != nil {
		t.Fatalf("create temp file: %v", err)
	}
	tmpFile.Close()

	db, err := lib.OpenDB(tmpFile.Name(), "../migrations/001_initial.sql")
	if err != nil {
		os.Remove(tmpFile.Name())
		t.Fatalf("OpenDB: %v", err)
	}

	masterKey := make([]byte, 32)
	for i := range masterKey {
		masterKey[i] = byte(i)
	}
	jwtSecret := []byte("test-jwt-secret-32-bytes-long!!")

	cfg := &lib.Config{
		MasterKey:    masterKey,
		JWTSecret:    jwtSecret,
		Env:          "development",
		BackdoorCode: "220402",
	}

	tmpDir, err := os.MkdirTemp("", "dealspace-store-org-test")
	if err != nil {
		db.Close()
		os.Remove(tmpFile.Name())
		t.Fatal(err)
	}
	store, _ := lib.NewLocalStore(tmpDir)

	router := NewRouter(db, cfg, store, nil, nil, nil)
	server := httptest.NewServer(router)

	cleanup := func() {
		server.Close()
		db.Close()
		os.Remove(tmpFile.Name())
		os.RemoveAll(tmpDir)
	}

	return server, db, cfg, cleanup
}

// createUserWithRole creates a user and grants them a role on a project
func createUserWithRole(t *testing.T, db *lib.DB, email, role, projectID string) string {
	t.Helper()
	now := time.Now().UnixMilli()
	userID := uuid.New().String()
	user := &lib.User{
		UserID:    userID,
		Email:     email,
		Name:      "Test User",
		Password:  "",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := lib.UserCreate(db, user); err != nil {
		t.Fatalf("UserCreate: %v", err)
	}

	if role != "" && projectID != "" {
		ops := "r"
		switch role {
		case lib.RoleSuperAdmin, lib.RoleIBAdmin:
			ops = "rwdm"
		case lib.RoleIBMember:
			ops = "rwdm"
		case lib.RoleSellerAdmin, lib.RoleSellerMember:
			ops = "rwd"
		}

		lib.AccessGrant(db, &lib.Access{
			ID:        uuid.New().String(),
			ProjectID: projectID,
			UserID:    userID,
			Role:      role,
			Ops:       ops,
			CanGrant:  role == lib.RoleIBAdmin || role == lib.RoleSuperAdmin,
			GrantedBy: "system",
			GrantedAt: now,
		})
	}

	return userID
}

// createSuperAdmin creates a super admin user
func createSuperAdmin(t *testing.T, db *lib.DB, email string) string {
	t.Helper()
	now := time.Now().UnixMilli()
	userID := uuid.New().String()
	user := &lib.User{
		UserID:    userID,
		Email:     email,
		Name:      "Super Admin",
		Password:  "",
		Active:    true,
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := lib.UserCreate(db, user); err != nil {
		t.Fatalf("UserCreate: %v", err)
	}

	lib.AccessGrant(db, &lib.Access{
		ID:        uuid.New().String(),
		ProjectID: "global",
		UserID:    userID,
		Role:      lib.RoleSuperAdmin,
		Ops:       "rwdm",
		CanGrant:  true,
		GrantedBy: "system",
		GrantedAt: now,
	})

	return userID
}

// getToken gets a token for a user via backdoor auth
func getToken(t *testing.T, client *http.Client, serverURL, email string) string {
	t.Helper()

	// Challenge
	body, _ := json.Marshal(map[string]string{"email": email})
	resp, err := client.Post(serverURL+"/api/auth/challenge", "application/json", bytes.NewReader(body))
	if err != nil {
		t.Fatalf("challenge failed: %v", err)
	}
	resp.Body.Close()

	// Verify with backdoor
	body, _ = json.Marshal(map[string]string{"email": email, "code": "220402"})
	resp, err = client.Post(serverURL+"/api/auth/verify", "application/json", bytes.NewReader(body))
	if err != nil {
		t.Fatalf("verify failed: %v", err)
	}
	var verifyResp map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&verifyResp)
	resp.Body.Close()

	token, ok := verifyResp["token"].(string)
	if !ok || token == "" {
		t.Fatalf("no token in verify response: %v", verifyResp)
	}
	return token
}

func TestCreateOrg(t *testing.T) {
	server, db, _, cleanup := testServerSetup(t)
	defer cleanup()

	client := &http.Client{}

	// Create an IB admin user (can create orgs)
	projectID := uuid.New().String()
	createUserWithRole(t, db, "ibadmin@test.com", lib.RoleIBAdmin, projectID)
	token := getToken(t, client, server.URL, "ibadmin@test.com")

	// Create org with valid payload
	orgBody, _ := json.Marshal(map[string]interface{}{
		"name":          "Acme Corporation",
		"domains":       []string{"acme.com", "acme-corp.com"},
		"role":          "seller",
		"website":       "https://acme.com",
		"description":   "A company that makes everything",
		"contact_name":  "Wile E. Coyote",
		"contact_email": "wile@acme.com",
	})

	req, _ := http.NewRequest("POST", server.URL+"/api/orgs", bytes.NewReader(orgBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("create org request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated {
		var errResp map[string]string
		json.NewDecoder(resp.Body).Decode(&errResp)
		t.Fatalf("expected 201, got %d: %v", resp.StatusCode, errResp)
	}

	var createResp map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&createResp)

	if createResp["entry_id"] == nil || createResp["entry_id"] == "" {
		t.Error("response should contain entry_id")
	}
	if createResp["name"] != "Acme Corporation" {
		t.Errorf("name mismatch: got %v", createResp["name"])
	}
	if createResp["role"] != "seller" {
		t.Errorf("role mismatch: got %v", createResp["role"])
	}

	domains, ok := createResp["domains"].([]interface{})
	if !ok || len(domains) != 2 {
		t.Errorf("domains should have 2 entries, got %v", createResp["domains"])
	}
}

func TestCreateOrgEmptyDomains(t *testing.T) {
	server, db, _, cleanup := testServerSetup(t)
	defer cleanup()

	client := &http.Client{}

	projectID := uuid.New().String()
	createUserWithRole(t, db, "ibadmin2@test.com", lib.RoleIBAdmin, projectID)
	token := getToken(t, client, server.URL, "ibadmin2@test.com")

	// Try to create org with empty domains array
	orgBody, _ := json.Marshal(map[string]interface{}{
		"name":    "Bad Org",
		"domains": []string{},
		"role":    "buyer",
	})

	req, _ := http.NewRequest("POST", server.URL+"/api/orgs", bytes.NewReader(orgBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Errorf("expected 400 for empty domains, got %d", resp.StatusCode)
	}

	var errResp map[string]string
	json.NewDecoder(resp.Body).Decode(&errResp)
	if errResp["code"] != "missing_fields" {
		t.Errorf("expected missing_fields error code, got %s", errResp["code"])
	}
}

func TestCreateOrgMissingDomains(t *testing.T) {
	server, db, _, cleanup := testServerSetup(t)
	defer cleanup()

	client := &http.Client{}

	projectID := uuid.New().String()
	createUserWithRole(t, db, "ibadmin3@test.com", lib.RoleIBAdmin, projectID)
	token := getToken(t, client, server.URL, "ibadmin3@test.com")

	// Try to create org without domains field at all
	orgBody, _ := json.Marshal(map[string]interface{}{
		"name": "Missing Domains Org",
		"role": "seller",
	})

	req, _ := http.NewRequest("POST", server.URL+"/api/orgs", bytes.NewReader(orgBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Errorf("expected 400 for missing domains, got %d", resp.StatusCode)
	}
}

func TestCreateOrgEmptyDomainString(t *testing.T) {
	server, db, _, cleanup := testServerSetup(t)
	defer cleanup()

	client := &http.Client{}

	projectID := uuid.New().String()
	createUserWithRole(t, db, "ibadmin4@test.com", lib.RoleIBAdmin, projectID)
	token := getToken(t, client, server.URL, "ibadmin4@test.com")

	// Try to create org with empty string in domains
	orgBody, _ := json.Marshal(map[string]interface{}{
		"name":    "Empty String Domain Org",
		"domains": []string{"valid.com", ""},
		"role":    "seller",
	})

	req, _ := http.NewRequest("POST", server.URL+"/api/orgs", bytes.NewReader(orgBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Errorf("expected 400 for empty domain string, got %d", resp.StatusCode)
	}

	var errResp map[string]string
	json.NewDecoder(resp.Body).Decode(&errResp)
	if errResp["code"] != "invalid_domains" {
		t.Errorf("expected invalid_domains error code, got %s", errResp["code"])
	}
}

func TestListOrgs(t *testing.T) {
	server, db, _, cleanup := testServerSetup(t)
	defer cleanup()

	client := &http.Client{}

	// Create IB admin to create orgs
	projectID := uuid.New().String()
	createUserWithRole(t, db, "ibadmin5@test.com", lib.RoleIBAdmin, projectID)
	ibToken := getToken(t, client, server.URL, "ibadmin5@test.com")

	// Create two orgs
	for i, name := range []string{"Org Alpha", "Org Beta"} {
		orgBody, _ := json.Marshal(map[string]interface{}{
			"name":    name,
			"domains": []string{name + ".com"},
			"role":    "seller",
		})
		req, _ := http.NewRequest("POST", server.URL+"/api/orgs", bytes.NewReader(orgBody))
		req.Header.Set("Authorization", "Bearer "+ibToken)
		req.Header.Set("Content-Type", "application/json")
		resp, _ := client.Do(req)
		if resp.StatusCode != http.StatusCreated {
			t.Fatalf("failed to create org %d: %d", i, resp.StatusCode)
		}
		resp.Body.Close()
	}

	// Create super_admin to list all orgs (IB admin only sees orgs linked to their projects via deal_org)
	createSuperAdmin(t, db, "superadmin-list@test.com")
	saToken := getToken(t, client, server.URL, "superadmin-list@test.com")

	// List orgs as super_admin
	req, _ := http.NewRequest("GET", server.URL+"/api/orgs", nil)
	req.Header.Set("Authorization", "Bearer "+saToken)
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("list orgs failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}

	var orgs []map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&orgs)

	if len(orgs) < 2 {
		t.Errorf("super_admin expected at least 2 orgs, got %d", len(orgs))
	}
}

func TestSuperAdminCanListAllOrgs(t *testing.T) {
	server, db, _, cleanup := testServerSetup(t)
	defer cleanup()

	client := &http.Client{}

	// Create super admin first
	createSuperAdmin(t, db, "superadmin@test.com")

	// Create an IB admin to create orgs
	projectID := uuid.New().String()
	createUserWithRole(t, db, "ibadmin6@test.com", lib.RoleIBAdmin, projectID)
	ibToken := getToken(t, client, server.URL, "ibadmin6@test.com")

	// Create org
	orgBody, _ := json.Marshal(map[string]interface{}{
		"name":    "SuperAdmin Test Org",
		"domains": []string{"supertest.com"},
		"role":    "buyer",
	})
	req, _ := http.NewRequest("POST", server.URL+"/api/orgs", bytes.NewReader(orgBody))
	req.Header.Set("Authorization", "Bearer "+ibToken)
	req.Header.Set("Content-Type", "application/json")
	resp, _ := client.Do(req)
	resp.Body.Close()

	// Super admin lists orgs
	saToken := getToken(t, client, server.URL, "superadmin@test.com")
	req, _ = http.NewRequest("GET", server.URL+"/api/orgs", nil)
	req.Header.Set("Authorization", "Bearer "+saToken)
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("list orgs as super admin failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected 200, got %d", resp.StatusCode)
	}

	var orgs []map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&orgs)

	// Super admin should see all orgs
	found := false
	for _, org := range orgs {
		if org["name"] == "SuperAdmin Test Org" {
			found = true
			break
		}
	}
	if !found {
		t.Error("super admin should see all orgs")
	}
}

func TestCreateOrgForbiddenForRegularUser(t *testing.T) {
	server, db, _, cleanup := testServerSetup(t)
	defer cleanup()

	client := &http.Client{}

	// Create a regular user (buyer role)
	projectID := uuid.New().String()
	createUserWithRole(t, db, "buyer@test.com", lib.RoleBuyerMember, projectID)
	token := getToken(t, client, server.URL, "buyer@test.com")

	// Try to create org (should fail - only IB admin or super admin can create orgs)
	orgBody, _ := json.Marshal(map[string]interface{}{
		"name":    "Forbidden Org",
		"domains": []string{"forbidden.com"},
		"role":    "buyer",
	})

	req, _ := http.NewRequest("POST", server.URL+"/api/orgs", bytes.NewReader(orgBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusForbidden {
		t.Errorf("expected 403 for regular user, got %d", resp.StatusCode)
	}
}

func TestGetOrg(t *testing.T) {
	server, db, _, cleanup := testServerSetup(t)
	defer cleanup()

	client := &http.Client{}

	projectID := uuid.New().String()
	createUserWithRole(t, db, "ibadmin7@test.com", lib.RoleIBAdmin, projectID)
	token := getToken(t, client, server.URL, "ibadmin7@test.com")

	// Create org
	orgBody, _ := json.Marshal(map[string]interface{}{
		"name":          "Get Test Org",
		"domains":       []string{"gettest.com"},
		"role":          "seller",
		"contact_email": "contact@gettest.com",
	})
	req, _ := http.NewRequest("POST", server.URL+"/api/orgs", bytes.NewReader(orgBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, _ := client.Do(req)
	var createResp map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&createResp)
	resp.Body.Close()

	orgID := createResp["entry_id"].(string)

	// Get the org
	req, _ = http.NewRequest("GET", server.URL+"/api/orgs/"+orgID, nil)
	req.Header.Set("Authorization", "Bearer "+token)
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("get org failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}

	var orgResp map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&orgResp)

	if orgResp["name"] != "Get Test Org" {
		t.Errorf("name mismatch: got %v", orgResp["name"])
	}
	if orgResp["contact_email"] != "contact@gettest.com" {
		t.Errorf("contact_email mismatch: got %v", orgResp["contact_email"])
	}
}

func TestUpdateOrg(t *testing.T) {
	server, db, _, cleanup := testServerSetup(t)
	defer cleanup()

	client := &http.Client{}

	// Use super_admin for update (org entries use their own ID as project_id, need super_admin access)
	createSuperAdmin(t, db, "superadmin-update@test.com")
	token := getToken(t, client, server.URL, "superadmin-update@test.com")

	// Create org
	orgBody, _ := json.Marshal(map[string]interface{}{
		"name":    "Update Test Org",
		"domains": []string{"update.com"},
		"role":    "seller",
	})
	req, _ := http.NewRequest("POST", server.URL+"/api/orgs", bytes.NewReader(orgBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, _ := client.Do(req)
	var createResp map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&createResp)
	resp.Body.Close()

	orgID := createResp["entry_id"].(string)

	// Update the org
	updateBody, _ := json.Marshal(map[string]interface{}{
		"name":        "Updated Org Name",
		"domains":     []string{"update.com", "updated.com"},
		"description": "Now with a description",
		"version":     1,
	})
	req, _ = http.NewRequest("PATCH", server.URL+"/api/orgs/"+orgID, bytes.NewReader(updateBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("update org failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		var errResp map[string]string
		json.NewDecoder(resp.Body).Decode(&errResp)
		t.Fatalf("expected 200, got %d: %v", resp.StatusCode, errResp)
	}

	var updateResp map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&updateResp)

	if updateResp["name"] != "Updated Org Name" {
		t.Errorf("name should be updated: got %v", updateResp["name"])
	}
	if updateResp["description"] != "Now with a description" {
		t.Errorf("description should be updated: got %v", updateResp["description"])
	}
}

func TestCreateOrgInvalidRole(t *testing.T) {
	server, db, _, cleanup := testServerSetup(t)
	defer cleanup()

	client := &http.Client{}

	projectID := uuid.New().String()
	createUserWithRole(t, db, "ibadmin9@test.com", lib.RoleIBAdmin, projectID)
	token := getToken(t, client, server.URL, "ibadmin9@test.com")

	// Try to create org with invalid role
	orgBody, _ := json.Marshal(map[string]interface{}{
		"name":    "Invalid Role Org",
		"domains": []string{"invalid.com"},
		"role":    "invalid_role",
	})

	req, _ := http.NewRequest("POST", server.URL+"/api/orgs", bytes.NewReader(orgBody))
	req.Header.Set("Authorization", "Bearer "+token)
	req.Header.Set("Content-Type", "application/json")
	resp, err := client.Do(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Errorf("expected 400 for invalid role, got %d", resp.StatusCode)
	}

	var errResp map[string]string
	json.NewDecoder(resp.Body).Decode(&errResp)
	if errResp["code"] != "invalid_role" {
		t.Errorf("expected invalid_role error code, got %s", errResp["code"])
	}
}
