//
//  Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//

package service

import (
	"testing"
	_ "unsafe"

	"github.com/glebarez/sqlite"
	"gorm.io/gorm"

	"ragflow/internal/common"
	"ragflow/internal/dao"
	"ragflow/internal/entity"
	"ragflow/internal/entity/models"
)

//go:linkname daoModelProviderManager ragflow/internal/dao.modelProviderManager
var daoModelProviderManager *models.ProviderManager

// TestListMembersAuthCheck verifies that a non-owner (userID != tenantID) gets
// CodeAuthenticationError without hitting the database.
func TestListMembersAuthCheck(t *testing.T) {
	s := &TenantService{}
	ctx := t.Context()
	_, code, err := s.ListMembers(ctx, "user-abc", "tenant-xyz")
	if err == nil {
		t.Fatal("expected error for non-owner, got nil")
	}
	if code != common.CodeAuthenticationError {
		t.Errorf("expected CodeAuthenticationError, got %v", code)
	}
}

// TestAddMemberAuthCheck verifies that a non-owner gets CodeAuthenticationError.
func TestAddMemberAuthCheck(t *testing.T) {
	s := &TenantService{}
	ctx := t.Context()
	_, code, err := s.AddMember(ctx, "user-abc", "tenant-xyz", &AddMemberRequest{Email: "a@b.com"})
	if err == nil {
		t.Fatal("expected error for non-owner, got nil")
	}
	if code != common.CodeAuthenticationError {
		t.Errorf("expected CodeAuthenticationError, got %v", code)
	}
}

// TestAddMemberEmailRequired verifies the email validation runs after the auth check.
func TestAddMemberEmailRequired(t *testing.T) {
	// When userID == tenantID (owner) but no email, expect CodeArgumentError.
	s := &TenantService{}
	ctx := t.Context()
	_, code, err := s.AddMember(ctx, "owner-id", "owner-id", &AddMemberRequest{Email: ""})
	if err == nil {
		t.Fatal("expected error for empty email, got nil")
	}
	if code != common.CodeArgumentError {
		t.Errorf("expected CodeArgumentError, got %v", code)
	}
}

// TestRemoveMemberAuthCheck verifies that an unrelated user gets CodeAuthenticationError.
func TestRemoveMemberAuthCheck(t *testing.T) {
	s := &TenantService{}
	ctx := t.Context()
	code, err := s.RemoveMember(ctx, "user-abc", "tenant-xyz", "user-def")
	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if code != common.CodeAuthenticationError {
		t.Errorf("expected CodeAuthenticationError, got %v", code)
	}
}

// TestRemoveMemberSelfAllowed verifies that a user removing themselves passes the auth check.
// The DAO is nil so the operation fails at the data layer, but the auth check must pass first.
func TestRemoveMemberSelfAllowed(t *testing.T) {
	s := &TenantService{}
	// userID == targetUserID: auth check should pass.
	ctx := t.Context()
	code, err := s.RemoveMember(ctx, "user-abc", "tenant-xyz", "user-abc")
	if code == common.CodeAuthenticationError {
		t.Errorf("self-removal should pass auth check, got CodeAuthenticationError: %v", err)
	}
	if code != common.CodeServerError {
		t.Errorf("expected CodeServerError (DAO not initialized), got %v", code)
	}
	if err == nil {
		t.Error("expected non-nil error when userTenantDAO is nil")
	}
}

// TestAcceptInviteAuthCheck verifies that AcceptInvite fails when DAO is not initialized.
func TestAcceptInviteAuthCheck(t *testing.T) {
	s := &TenantService{}
	// nil userTenantDAO: nil guard returns CodeServerError.
	ctx := t.Context()
	code, err := s.AcceptInvite(ctx, "user-abc", "tenant-xyz")
	if err == nil {
		t.Fatal("expected error when userTenantDAO is nil, got nil")
	}
	if code != common.CodeServerError {
		t.Errorf("expected CodeServerError (DAO not initialized), got %v", code)
	}
}

// TestTenantRoleConstants verifies the role string values match the Python enums.
func TestTenantRoleConstants(t *testing.T) {
	cases := map[string]string{
		TenantRoleOwner:  "owner",
		TenantRoleNormal: "normal",
		TenantRoleInvite: "invite",
		TenantRoleAdmin:  "admin",
	}
	for got, want := range cases {
		if got != want {
			t.Errorf("role constant = %q, want %q", got, want)
		}
	}
}

// TestSetTenantDefaultModels_WithModelID verifies that SetTenantDefaultModels
// correctly resolves a modelID to composite name, validates ownership, and updates the tenant.
func TestSetTenantDefaultModels_WithModelID(t *testing.T) {
	// 1. Setup SQLite in-memory DB
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		TranslateError: true,
	})
	if err != nil {
		t.Fatalf("failed to open sqlite: %v", err)
	}

	err = models.InitProviderManager("../../conf/models")
	if err != nil {
		t.Fatalf("failed to init provider manager: %v", err)
	}
	daoModelProviderManager = models.GetProviderManager()

	// 2. Migrate tables
	err = db.AutoMigrate(
		&entity.Tenant{},
		&entity.TenantModelProvider{},
		&entity.TenantModelInstance{},
		&entity.TenantModel{},
		&entity.UserTenant{},
	)
	if err != nil {
		t.Fatalf("failed to auto migrate: %v", err)
	}

	// Swap dao.DB for the test
	origDB := dao.DB
	dao.DB = db
	defer func() { dao.DB = origDB }()

	// 3. Insert mock data
	tenantID := "tenant-123"
	userID := "user-123"
	statusVal := "1"

	// Insert UserTenant
	err = db.Create(&entity.UserTenant{
		ID:       "ut-1",
		UserID:   userID,
		TenantID: tenantID,
		Role:     "owner",
		Status:   &statusVal,
	}).Error
	if err != nil {
		t.Fatalf("failed to create user tenant: %v", err)
	}

	// Insert Tenant
	err = db.Create(&entity.Tenant{
		ID:     tenantID,
		LLMID:  "",
		EmbdID: "",
		ASRID:  "",
		Status: &statusVal,
	}).Error
	if err != nil {
		t.Fatalf("failed to create tenant: %v", err)
	}

	// Insert Provider
	providerID := "provider-1"
	err = db.Create(&entity.TenantModelProvider{
		ID:           providerID,
		TenantID:     tenantID,
		ProviderName: "OpenAI",
	}).Error
	if err != nil {
		t.Fatalf("failed to create provider: %v", err)
	}

	// Insert Real Instance (for checkModelAvailable lookup)
	err = db.Create(&entity.TenantModelInstance{
		ID:           "instance-real",
		ProviderID:   providerID,
		InstanceName: "default",
	}).Error
	if err != nil {
		t.Fatalf("failed to create real instance: %v", err)
	}

	// Insert Dummy Instance (associated with the model record)
	err = db.Create(&entity.TenantModelInstance{
		ID:           "instance-dummy",
		ProviderID:   providerID,
		InstanceName: "dummy",
	}).Error
	if err != nil {
		t.Fatalf("failed to create dummy instance: %v", err)
	}

	// Insert Model pointing to instance-dummy
	modelID := "model-1"
	err = db.Create(&entity.TenantModel{
		ID:         modelID,
		ModelName:  "gpt-4o",
		ProviderID: providerID,
		InstanceID: "instance-dummy",
		ModelType:  int(entity.ModelTypeChat),
		Status:     "active",
	}).Error
	if err != nil {
		t.Fatalf("failed to create model: %v", err)
	}

	// 4. Run SetTenantDefaultModels
	s := NewTenantService()
	// Set chat model using modelID, explicitly passing "default" as instance name to bypass pre-existing checkModelAvailable panic
	ctx := t.Context()
	err = s.SetTenantDefaultModels(ctx, userID, "", "default", "", "chat", modelID)
	if err != nil {
		t.Fatalf("SetTenantDefaultModels failed: %v", err)
	}

	// Verify Tenant default model is updated to composite name
	tenant := &entity.Tenant{}
	err = db.Where("id = ?", tenantID).First(tenant).Error
	if err != nil {
		t.Fatalf("failed to retrieve tenant: %v", err)
	}

	expectedDefaultModel := "gpt-4o@dummy@OpenAI"
	if tenant.LLMID != expectedDefaultModel {
		t.Errorf("expected tenant default LLM to be %q, got %q", expectedDefaultModel, tenant.LLMID)
	}
	if tenant.TenantLLMID == nil || *tenant.TenantLLMID != modelID {
		t.Errorf("expected tenant_llm_id to be %q, got %v", modelID, tenant.TenantLLMID)
	}
}
