//
//  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 (
	"strings"
	"testing"

	"ragflow/internal/common"
	"ragflow/internal/entity"
)

// ---------------------------------------------------------------------------
// CreateSession contract — mirrors the assertions in
// test_session_create_validation_and_deleted_chat_contract that exercise the
// Go service layer (name validation -> 102, auth -> 109, truncation, success).
// ---------------------------------------------------------------------------

func TestCreateSession_Success(t *testing.T) {
	store := newFakeSessionStore()
	store.dialogExists["user-1|chat-1"] = true
	store.dialogs["chat-1"] = &entity.Chat{ID: "chat-1", PromptConfig: entity.JSONMap{"prologue": "hi"}}

	svc := &ChatSessionService{
		chatSessionDAO: store,
		userTenantDAO:  &fakeTenantStore{},
		pipeline:       &fakePipeline{},
	}

	ctx := t.Context()
	resp, code, err := svc.CreateSession(ctx, "user-1", "chat-1", map[string]interface{}{"name": "valid"})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if code != common.CodeSuccess {
		t.Fatalf("code=%v", code)
	}
	if resp.Name == nil || *resp.Name != "valid" {
		t.Fatalf("name=%v", resp.Name)
	}
	if resp.ChatID != "chat-1" {
		t.Fatalf("chat_id=%q", resp.ChatID)
	}
	if len(store.sessions) != 1 {
		t.Fatalf("expected 1 session created, got %d", len(store.sessions))
	}
}

func TestCreateSession_RejectsEmptyOrNonStringName(t *testing.T) {
	store := newFakeSessionStore()
	store.dialogExists["user-1|chat-1"] = true
	store.dialogs["chat-1"] = &entity.Chat{ID: "chat-1"}

	svc := &ChatSessionService{
		chatSessionDAO: store,
		userTenantDAO:  &fakeTenantStore{},
		pipeline:       &fakePipeline{},
	}

	ctx := t.Context()
	for _, name := range []interface{}{"", "   ", 1} {
		_, code, err := svc.CreateSession(ctx, "user-1", "chat-1", map[string]interface{}{"name": name})
		if err == nil || err.Error() != "`name` can not be empty" {
			t.Fatalf("name=%#v err=%v", name, err)
		}
		if code != common.CodeDataError {
			t.Fatalf("name=%#v code=%v", name, code)
		}
	}
}

func TestCreateSession_TruncatesLongName(t *testing.T) {
	store := newFakeSessionStore()
	store.dialogExists["user-1|chat-1"] = true
	store.dialogs["chat-1"] = &entity.Chat{ID: "chat-1"}

	svc := &ChatSessionService{
		chatSessionDAO: store,
		userTenantDAO:  &fakeTenantStore{},
		pipeline:       &fakePipeline{},
	}

	ctx := t.Context()
	longName := strings.Repeat("a", 300)
	resp, code, err := svc.CreateSession(ctx, "user-1", "chat-1", map[string]interface{}{"name": longName})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if code != common.CodeSuccess {
		t.Fatalf("code=%v", code)
	}
	if resp.Name == nil || len([]rune(*resp.Name)) != 255 {
		t.Fatalf("expected name truncated to 255 runes, got %v", resp.Name)
	}
}

func TestCreateSession_NotOwner(t *testing.T) {
	store := newFakeSessionStore()
	svc := &ChatSessionService{
		chatSessionDAO: store,
		userTenantDAO:  &fakeTenantStore{},
		pipeline:       &fakePipeline{},
	}

	ctx := t.Context()
	_, code, err := svc.CreateSession(ctx, "user-1", "chat-1", map[string]interface{}{"name": "x"})
	if err == nil || err.Error() != "No authorization." {
		t.Fatalf("err=%v", err)
	}
	if code != common.CodeAuthenticationError {
		t.Fatalf("code=%v", code)
	}
}

// ---------------------------------------------------------------------------
// DeleteSessions contract — mirrors the service-layer assertions in
// test_session_delete_basic_scenarios.
// ---------------------------------------------------------------------------

func TestDeleteSessions_SuccessByIDs(t *testing.T) {
	store := newFakeSessionStore()
	store.dialogExists["user-1|chat-1"] = true
	store.sessions["s1"] = &entity.ChatSession{ID: "s1", DialogID: "chat-1"}
	store.sessions["s2"] = &entity.ChatSession{ID: "s2", DialogID: "chat-1"}

	svc := &ChatSessionService{
		chatSessionDAO: store,
		userTenantDAO:  &fakeTenantStore{},
		pipeline:       &fakePipeline{},
	}

	ctx := t.Context()
	result, message, code, err := svc.DeleteSessions(ctx, "user-1", "chat-1", map[string]interface{}{"ids": []interface{}{"s1"}})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if code != common.CodeSuccess {
		t.Fatalf("code=%v", code)
	}
	if message != "success" {
		t.Fatalf("message=%q", message)
	}
	if result != true {
		t.Fatalf("result=%v", result)
	}
	if len(store.sessions) != 1 {
		t.Fatalf("expected 1 session remaining, got %d", len(store.sessions))
	}
}

func TestDeleteSessions_DeleteAllAndInvalidID(t *testing.T) {
	store := newFakeSessionStore()
	store.dialogExists["user-1|chat-1"] = true

	svc := &ChatSessionService{
		chatSessionDAO: store,
		userTenantDAO:  &fakeTenantStore{},
		pipeline:       &fakePipeline{},
	}

	// Empty payload -> success with empty result map.
	ctx := t.Context()
	if _, _, code, err := svc.DeleteSessions(ctx, "user-1", "chat-1", map[string]interface{}{}); err != nil || code != common.CodeSuccess {
		t.Fatalf("empty payload: code=%v err=%v", code, err)
	}

	// delete_all removes every session for the chat.
	store.sessions["s1"] = &entity.ChatSession{ID: "s1", DialogID: "chat-1"}
	if _, _, code, err := svc.DeleteSessions(ctx, "user-1", "chat-1", map[string]interface{}{"delete_all": true}); err != nil || code != common.CodeSuccess {
		t.Fatalf("delete_all: code=%v err=%v", code, err)
	}
	if len(store.sessions) != 0 {
		t.Fatalf("delete_all should remove all, got %d", len(store.sessions))
	}

	// Unknown id -> DataError reporting the unowned session.
	store.sessions["s1"] = &entity.ChatSession{ID: "s1", DialogID: "chat-1"}
	_, _, code, err := svc.DeleteSessions(ctx, "user-1", "chat-1", map[string]interface{}{"ids": []interface{}{"missing"}})
	if err == nil || !strings.Contains(err.Error(), "The chat doesn't own the session missing") {
		t.Fatalf("invalid id: err=%v", err)
	}
	if code != common.CodeDataError {
		t.Fatalf("invalid id code=%v", code)
	}
}

func TestDeleteSessions_NotOwner(t *testing.T) {
	store := newFakeSessionStore()
	svc := &ChatSessionService{
		chatSessionDAO: store,
		userTenantDAO:  &fakeTenantStore{},
		pipeline:       &fakePipeline{},
	}

	ctx := t.Context()
	_, _, code, err := svc.DeleteSessions(ctx, "user-1", "chat-1", map[string]interface{}{"ids": []interface{}{"s1"}})
	if err == nil || err.Error() != "No authorization." {
		t.Fatalf("err=%v", err)
	}
	if code != common.CodeAuthenticationError {
		t.Fatalf("code=%v", code)
	}
}
