package platform

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"errors"
	"io"
	"strings"
	"testing"
)

func init() {
	sql.Register("tdengine_missing_table_test", missingTableDriver{})
	sql.Register("ops_health_test", opsHealthDriver{})
}

type missingTableDriver struct{}

func (missingTableDriver) Open(_ string) (driver.Conn, error) {
	return missingTableConn{}, nil
}

type missingTableConn struct{}

func (missingTableConn) Prepare(_ string) (driver.Stmt, error) {
	return nil, errors.New("not implemented")
}

func (missingTableConn) Close() error {
	return nil
}

func (missingTableConn) Begin() (driver.Tx, error) {
	return nil, errors.New("not implemented")
}

func (missingTableConn) QueryContext(_ context.Context, _ string, _ []driver.NamedValue) (driver.Rows, error) {
	return nil, errors.New("[0x2603] Fail to get table info, error: Table does not exist")
}

type emptyRows struct{}

func (emptyRows) Columns() []string {
	return []string{}
}

func (emptyRows) Close() error {
	return nil
}

func (emptyRows) Next(_ []driver.Value) error {
	return io.EOF
}

type opsHealthDriver struct{}

func (opsHealthDriver) Open(_ string) (driver.Conn, error) {
	return opsHealthConn{}, nil
}

type opsHealthConn struct{}

func (opsHealthConn) Prepare(_ string) (driver.Stmt, error) {
	return nil, errors.New("not implemented")
}

func (opsHealthConn) Close() error {
	return nil
}

func (opsHealthConn) Begin() (driver.Tx, error) {
	return nil, errors.New("not implemented")
}

func (opsHealthConn) Ping(_ context.Context) error {
	return nil
}

func (opsHealthConn) QueryContext(_ context.Context, _ string, _ []driver.NamedValue) (driver.Rows, error) {
	return &singleValueRows{columns: []string{"ok"}, values: []driver.Value{1}}, nil
}

type singleValueRows struct {
	columns []string
	values  []driver.Value
	read    bool
}

func (r singleValueRows) Columns() []string {
	return r.columns
}

func (r singleValueRows) Close() error {
	return nil
}

func (r *singleValueRows) Next(dest []driver.Value) error {
	if r.read {
		return io.EOF
	}
	r.read = true
	copy(dest, r.values)
	return nil
}

type fakeRedisOnlineProbe struct {
	count int
	err   error
}

func (p fakeRedisOnlineProbe) CountOnlineKeys(context.Context) (int, error) {
	return p.count, p.err
}

type fakeCapacityCheckProbe struct {
	result CapacityCheckResult
	err    error
}

func (p fakeCapacityCheckProbe) CheckCapacity(context.Context) (CapacityCheckResult, error) {
	return p.result, p.err
}

func TestBuildVehicleServiceOverviewBatchSQLUsesFuzzyKeywordMatching(t *testing.T) {
	built := buildVehicleServiceOverviewBatchSQL(VehicleOverviewBatchQuery{
		Keywords: []string{"AG183", "R0LS1426"},
		Protocol: "JT808",
	})
	if !strings.Contains(built.Text, "LIKE ?") {
		t.Fatalf("batch overview SQL should support fuzzy keyword matching, got %s", built.Text)
	}
	if !strings.Contains(built.Text, "b.oem LIKE ?") {
		t.Fatalf("batch overview SQL should include OEM in vehicle identity search, got %s", built.Text)
	}
	if len(built.Args) == 0 || built.Args[0] != "%AG183%" {
		t.Fatalf("batch overview SQL should bind fuzzy keyword args first, got %#v", built.Args)
	}
	if got := built.Args[len(built.Args)-1]; got != "JT808" {
		t.Fatalf("protocol should remain an exact filter, got %#v args=%#v", got, built.Args)
	}
}

func TestTDengineTableNotExistErrorIsRecognized(t *testing.T) {
	err := errors.New("[0x2603] Fail to get table info, error: Table does not exist")
	if !isTDengineTableNotExist(err) {
		t.Fatalf("TDengine table-not-exist error should be recognized: %v", err)
	}
	if isTDengineTableNotExist(errors.New("connection refused")) {
		t.Fatalf("unrelated errors should not be treated as empty table results")
	}
}

func TestRawFramesReturnsEmptyPageWhenTDengineTableIsMissing(t *testing.T) {
	tdengine, err := sql.Open("tdengine_missing_table_test", "")
	if err != nil {
		t.Fatalf("open test tdengine: %v", err)
	}
	defer tdengine.Close()
	store := &ProductionStore{tdengine: tdengine, tdDatabase: "vehicle_test"}

	page, err := store.RawFrames(context.Background(), RawFrameQuery{
		Protocol:      "GB32960",
		VIN:           "VIN_WITHOUT_SUBTABLE",
		Limit:         20,
		Offset:        40,
		IncludeFields: true,
	})
	if err != nil {
		t.Fatalf("missing raw frame table should return an empty page, got error: %v", err)
	}
	if page.Total != 0 || len(page.Items) != 0 || page.Limit != 20 || page.Offset != 40 {
		t.Fatalf("missing raw frame table should preserve pagination with empty items, got %+v", page)
	}
	if page.Items == nil {
		t.Fatalf("missing raw frame table should return a non-nil empty item list")
	}
}

func TestOpsHealthUsesRedisOnlineKeyProbe(t *testing.T) {
	db, err := sql.Open("ops_health_test", "")
	if err != nil {
		t.Fatalf("open ops health db: %v", err)
	}
	defer db.Close()
	store := &ProductionStore{db: db, redisOnline: fakeRedisOnlineProbe{count: 73}}

	health, err := store.OpsHealth(context.Background())
	if err != nil {
		t.Fatalf("OpsHealth returned error: %v", err)
	}
	if health.RedisOnlineKeys == nil || *health.RedisOnlineKeys != 73 {
		t.Fatalf("OpsHealth should expose Redis online key count, got %+v", health.RedisOnlineKeys)
	}
	var redisHealth *LinkHealth
	for index := range health.LinkHealth {
		if health.LinkHealth[index].Name == "Redis online keys" {
			redisHealth = &health.LinkHealth[index]
			break
		}
	}
	if redisHealth == nil || redisHealth.Status != "ok" || !strings.Contains(redisHealth.Detail, "73") {
		t.Fatalf("OpsHealth should expose Redis online key probe status, got %+v in %+v", redisHealth, health.LinkHealth)
	}
}

func TestOpsHealthUsesCapacityCheckProbeForKafkaLag(t *testing.T) {
	db, err := sql.Open("ops_health_test", "")
	if err != nil {
		t.Fatalf("open ops health db: %v", err)
	}
	defer db.Close()
	store := &ProductionStore{
		db: db,
		capacityCheck: fakeCapacityCheckProbe{result: CapacityCheckResult{
			Status: "degraded",
			CapacityMetrics: CapacityMetrics{
				ActiveConnections:     120000,
				KafkaLag:              42,
				BridgeConsumerPending: 1024,
				BridgeAckPending:      2,
			},
			Findings: []string{"kafka lag 42"},
		}},
	}

	health, err := store.OpsHealth(context.Background())
	if err != nil {
		t.Fatalf("OpsHealth returned error: %v", err)
	}
	if health.KafkaLag == nil || *health.KafkaLag != 42 {
		t.Fatalf("OpsHealth should expose capacity-check kafka lag, got %+v", health.KafkaLag)
	}
	if health.ActiveConnections == nil || *health.ActiveConnections != 120000 {
		t.Fatalf("OpsHealth should expose capacity-check active connections, got %+v", health.ActiveConnections)
	}
	if health.CapacityMetrics.BridgeConsumerPending != 1024 || health.CapacityMetrics.BridgeAckPending != 2 {
		t.Fatalf("OpsHealth should expose structured capacity metrics, got %+v", health.CapacityMetrics)
	}
	if len(health.CapacityFindings) != 1 || health.CapacityFindings[0] != "kafka lag 42" {
		t.Fatalf("OpsHealth should expose structured capacity findings, got %+v", health.CapacityFindings)
	}
	var capacityHealth *LinkHealth
	for index := range health.LinkHealth {
		if health.LinkHealth[index].Name == "Capacity check" {
			capacityHealth = &health.LinkHealth[index]
			break
		}
	}
	if capacityHealth == nil || capacityHealth.Status != "warning" || !strings.Contains(capacityHealth.Detail, "kafka lag 42") {
		t.Fatalf("OpsHealth should expose capacity-check warning, got %+v in %+v", capacityHealth, health.LinkHealth)
	}
}

func TestOpsHealthReturnsEmptyCapacityFindingsArray(t *testing.T) {
	db, err := sql.Open("ops_health_test", "")
	if err != nil {
		t.Fatalf("open ops health db: %v", err)
	}
	defer db.Close()
	store := &ProductionStore{
		db: db,
		capacityCheck: fakeCapacityCheckProbe{result: CapacityCheckResult{
			Status: "ok",
			CapacityMetrics: CapacityMetrics{
				ActiveConnections: 254,
				KafkaLag:          0,
			},
		}},
	}

	health, err := store.OpsHealth(context.Background())
	if err != nil {
		t.Fatalf("OpsHealth returned error: %v", err)
	}
	if health.CapacityFindings == nil || len(health.CapacityFindings) != 0 {
		t.Fatalf("OpsHealth should expose an empty capacity findings array, got %#v", health.CapacityFindings)
	}
}

func TestVehicleServiceOverviewMatchesPartialKeyword(t *testing.T) {
	overview := VehicleServiceOverview{
		VIN:   "LB9A32A24R0LS1426",
		Plate: "粤AG18312",
		Phone: "13307795425",
		OEM:   "G7s",
	}
	for _, keyword := range []string{"R0LS1426", "AG183", "077954", "g7"} {
		if !vehicleServiceOverviewMatchesKeyword(overview, keyword) {
			t.Fatalf("overview %+v should match partial keyword %q", overview, keyword)
		}
	}
	if vehicleServiceOverviewMatchesKeyword(overview, "不存在") {
		t.Fatalf("overview %+v should not match unrelated keyword", overview)
	}
}

func TestVehicleServiceOverviewDerivedFieldsIncludeSourceConsistency(t *testing.T) {
	overview := &VehicleServiceOverview{
		VIN:               "LB9A32A24R0LS1426",
		Protocols:         []string{"GB32960", "JT808"},
		SourceCount:       2,
		OnlineSourceCount: 1,
	}

	applyVehicleServiceOverviewDerivedFields(overview, true)

	if overview.SourceConsistency == nil {
		t.Fatalf("overview should include source consistency")
	}
	if overview.SourceConsistency.SourceCount != 2 || overview.SourceConsistency.OnlineSourceCount != 1 {
		t.Fatalf("source consistency should mirror overview source coverage, got %+v", overview.SourceConsistency)
	}
	if !containsString(overview.SourceConsistency.MissingProtocols, "YUTONG_MQTT") {
		t.Fatalf("source consistency should expose missing canonical source evidence, got %+v", overview.SourceConsistency)
	}
	if overview.ServiceStatus == nil || overview.ServiceStatus.Status != "degraded" {
		t.Fatalf("overview should keep canonical service status, got %+v", overview.ServiceStatus)
	}
}
