package roomnotice

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"
	"testing"
	"time"

	"google.golang.org/protobuf/proto"
	roomeventsv1 "hyapp.local/api/proto/events/room/v1"
	"hyapp/pkg/appcode"

	_ "github.com/go-sql-driver/mysql"
)

func TestMySQLRepositoryProcessesRealRoomKickOutbox(t *testing.T) {
	dsn := strings.TrimSpace(os.Getenv("NOTICE_REAL_MYSQL_DSN"))
	if dsn == "" {
		t.Skip("set NOTICE_REAL_MYSQL_DSN to run real MySQL notice validation")
	}
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		t.Fatalf("open mysql: %v", err)
	}
	defer db.Close()
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := db.PingContext(ctx); err != nil {
		t.Fatalf("ping mysql: %v", err)
	}

	suffix := fmt.Sprintf("%d", time.Now().UnixNano())
	roomDatabase := strings.TrimSpace(os.Getenv("NOTICE_REAL_ROOM_DATABASE"))
	if roomDatabase == "" {
		roomDatabase = "hyapp_room_notice_test_" + suffix
		if _, err := db.ExecContext(ctx, fmt.Sprintf(`CREATE DATABASE %s DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`, quoteDB(roomDatabase))); err != nil {
			t.Fatalf("create room test database: %v", err)
		}
		t.Cleanup(func() {
			_, _ = db.ExecContext(context.Background(), fmt.Sprintf(`DROP DATABASE %s`, quoteDB(roomDatabase)))
		})
	}
	if err := ensureRoomOutboxTable(ctx, db, roomDatabase); err != nil {
		t.Fatalf("ensure room_outbox table: %v", err)
	}

	repository, err := NewMySQLRepository(db, roomDatabase)
	if err != nil {
		t.Fatalf("NewMySQLRepository: %v", err)
	}
	appCode := "notice_real"
	roomID := "notice_real_room_" + suffix
	eventID := "notice_real_room_evt_" + suffix
	actorUserID := int64(990000001)
	targetUserID := int64(990000002)
	nowMS := time.Now().UnixMilli()

	body, err := proto.Marshal(&roomeventsv1.RoomUserKicked{
		ActorUserId:  actorUserID,
		TargetUserId: targetUserID,
	})
	if err != nil {
		t.Fatalf("marshal body: %v", err)
	}
	envelope, err := proto.Marshal(&roomeventsv1.EventEnvelope{
		EventId:      eventID,
		RoomId:       roomID,
		EventType:    "RoomUserKicked",
		RoomVersion:  12,
		OccurredAtMs: nowMS,
		AppCode:      appCode,
		Body:         body,
	})
	if err != nil {
		t.Fatalf("marshal envelope: %v", err)
	}

	if _, err := db.ExecContext(ctx, fmt.Sprintf(`
		INSERT INTO %s.room_outbox (
		  app_code, event_id, event_type, room_id, status, worker_id, lock_until_ms,
		  envelope, created_at_ms, retry_count, next_retry_at_ms, last_error, updated_at_ms
		) VALUES (?, ?, 'RoomUserKicked', ?, 'delivered', '', 0, ?, ?, 0, 0, '', ?)`,
		quoteDB(roomDatabase),
	), appCode, eventID, roomID, envelope, nowMS, nowMS); err != nil {
		t.Fatalf("insert room_outbox: %v", err)
	}
	if !keepRealRows() {
		t.Cleanup(func() {
			_, _ = db.ExecContext(context.Background(), `DELETE FROM notice_delivery_events WHERE app_code = ? AND source_event_id = ?`, appCode, eventID)
			_, _ = db.ExecContext(context.Background(), fmt.Sprintf(`DELETE FROM %s.room_outbox WHERE app_code = ? AND event_id = ?`, quoteDB(roomDatabase)), appCode, eventID)
		})
	}

	publisher := &fakePublisher{}
	service := New(Config{NodeID: "notice-real-test"}, repository, publisher)
	processed, err := service.ProcessRoomKickNotices(appcode.WithContext(ctx, appCode), RoomNoticeWorkerOptions{
		WorkerID:       "notice-real-room-test-worker",
		BatchSize:      1,
		LockTTL:        30 * time.Second,
		PublishTimeout: time.Second,
		MaxRetryCount:  3,
	})
	if err != nil {
		t.Fatalf("ProcessRoomKickNotices: %v", err)
	}
	if processed != 1 || len(publisher.messages) != 1 {
		t.Fatalf("unexpected process result: processed=%d messages=%d", processed, len(publisher.messages))
	}
	if publisher.messages[0].ToAccount != "990000002" || publisher.messages[0].Ext != "room_system_message" {
		t.Fatalf("unexpected private message: %+v", publisher.messages[0])
	}

	var noticeStatus string
	var deliveredAtMS int64
	var storedPayload string
	if err := db.QueryRowContext(ctx, `
		SELECT status, delivered_at_ms, COALESCE(CAST(payload_json AS CHAR), '{}')
		FROM notice_delivery_events
		WHERE source_name = 'room_outbox' AND app_code = ? AND source_event_id = ? AND channel = 'tencent_im_c2c'`,
		appCode, eventID,
	).Scan(&noticeStatus, &deliveredAtMS, &storedPayload); err != nil {
		t.Fatalf("query notice_delivery_events: %v", err)
	}
	if noticeStatus != "delivered" || deliveredAtMS <= 0 {
		t.Fatalf("unexpected delivery marker: status=%s delivered_at_ms=%d payload=%s", noticeStatus, deliveredAtMS, storedPayload)
	}
	var noticePayload map[string]any
	if err := json.Unmarshal([]byte(storedPayload), &noticePayload); err != nil {
		t.Fatalf("decode stored notice payload: %v", err)
	}
	if noticePayload["event_id"] != eventID || noticePayload["room_id"] != roomID || noticePayload["event_type"] != "room_user_kicked" {
		t.Fatalf("unexpected stored notice payload: %+v", noticePayload)
	}
	if noticePayload["target_user_id"] != strconv.FormatInt(targetUserID, 10) || noticePayload["actor_user_id"] != strconv.FormatInt(actorUserID, 10) {
		t.Fatalf("unexpected stored user payload: %+v", noticePayload)
	}
	t.Logf("real room notice validation event_id=%s status=%s delivered_at_ms=%d", eventID, noticeStatus, deliveredAtMS)
}

func keepRealRows() bool {
	value := strings.ToLower(strings.TrimSpace(os.Getenv("NOTICE_REAL_MYSQL_KEEP_ROWS")))
	return value == "1" || value == "true" || value == "yes"
}

func quoteDB(value string) string {
	return "`" + strings.ReplaceAll(value, "`", "``") + "`"
}

func ensureRoomOutboxTable(ctx context.Context, db *sql.DB, roomDatabase string) error {
	_, err := db.ExecContext(ctx, fmt.Sprintf(`
		CREATE TABLE IF NOT EXISTS %s.room_outbox (
		  app_code VARCHAR(32) NOT NULL,
		  event_id VARCHAR(128) NOT NULL,
		  event_type VARCHAR(64) NOT NULL,
		  room_id VARCHAR(64) NOT NULL,
		  status VARCHAR(32) NOT NULL,
		  worker_id VARCHAR(128) NOT NULL DEFAULT '',
		  lock_until_ms BIGINT NULL,
		  envelope LONGBLOB NOT NULL,
		  created_at_ms BIGINT NOT NULL,
		  retry_count INT NOT NULL DEFAULT 0,
		  next_retry_at_ms BIGINT NOT NULL DEFAULT 0,
		  last_error TEXT NULL,
		  updated_at_ms BIGINT NOT NULL,
		  PRIMARY KEY (app_code, event_id),
		  KEY idx_room_outbox_event_created (app_code, event_type, created_at_ms, event_id)
		) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
		quoteDB(roomDatabase),
	))
	return err
}
