package platform

import (
	"bytes"
	"context"
	"encoding/json"
	"regexp"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
)

func TestProductionVehicleSourcePolicyUsesStableEmptyAuditArray(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	store := NewProductionStore(db, nil, "")
	mock.ExpectQuery(`SELECT version, updated_by`).WithArgs("VIN001").
		WillReturnRows(sqlmock.NewRows([]string{"version", "updated_by", "updated_at"}))
	mock.ExpectQuery(`SELECT version, change_type, protocol, source_ref`).WithArgs("VIN001", "VIN001").
		WillReturnRows(sqlmock.NewRows([]string{"version", "change_type", "protocol", "source_ref", "source_label", "actor", "changed_at", "summary"}))
	config, err := store.VehicleSourcePolicy(context.Background(), "VIN001")
	if err != nil {
		t.Fatal(err)
	}
	if config.Audit == nil || len(config.Audit) != 0 {
		t.Fatalf("empty audit must be a stable slice: %#v", config.Audit)
	}
	encoded, err := json.Marshal(config)
	if err != nil {
		t.Fatal(err)
	}
	if string(encoded) == "" || !json.Valid(encoded) {
		t.Fatalf("policy JSON is invalid: %s", encoded)
	}
	if !bytes.Contains(encoded, []byte(`"audit":[]`)) {
		t.Fatalf("empty audit encoded as null: %s", encoded)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestProductionProviderUpdateKeepsPolicyRemarkSeparate(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	store := NewProductionStore(db, nil, "")
	update := vehicleSourcePolicyStoreUpdate{
		VehicleSourcePolicyUpdate: VehicleSourcePolicyUpdate{
			Version:          2,
			SourceRef:        "a-ref",
			ProviderName:     "G7s",
			ProviderEvidence: "GPS 运维终端清单 2026-07-16",
			Enabled:          true,
			Priority:         20,
			Remark:           "保持当前优先级",
			Actor:            "平台管理员",
		},
		VIN:                "VIN001",
		Protocol:           "JT808",
		SourceKey:          "opaque-source-key",
		SourceLabel:        "JT808 终端",
		CurrentEnabled:     true,
		CurrentPriority:    20,
		CurrentRemark:      "保持当前优先级",
		PolicyConfigurable: true,
	}

	mock.ExpectBegin()
	mock.ExpectExec(regexp.QuoteMeta(`INSERT IGNORE INTO platform_vehicle_source_policy_version
(vin, version, updated_by) VALUES (?, 1, 'system')`)).
		WithArgs("VIN001").WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT version FROM platform_vehicle_source_policy_version
WHERE vin = ? FOR UPDATE`)).
		WithArgs("VIN001").WillReturnRows(sqlmock.NewRows([]string{"version"}).AddRow(2))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT enabled, priority, remark
FROM vehicle_location_source_policy
WHERE vin = ? AND protocol = ? AND source_key = ?`)).
		WithArgs("VIN001", "JT808", "opaque-source-key").
		WillReturnRows(sqlmock.NewRows([]string{"enabled", "priority", "remark"}).AddRow(1, 20, "保持当前优先级"))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT provider_name
FROM platform_vehicle_source_provider
WHERE vin = ? AND protocol = ? AND source_key = ?`)).
		WithArgs("VIN001", "JT808", "opaque-source-key").
		WillReturnRows(sqlmock.NewRows([]string{"provider_name"}))
	mock.ExpectExec(`INSERT INTO platform_vehicle_source_provider`).
		WithArgs("VIN001", "JT808", "opaque-source-key", "a-ref", "G7s", "平台管理员").
		WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectExec(`UPDATE platform_vehicle_source_policy_version`).
		WithArgs(3, "平台管理员", "VIN001", 2).
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(`INSERT INTO platform_vehicle_source_provider_audit`).
		WithArgs(
			"VIN001", 3, "JT808", "a-ref", "opaque-source-key",
			"", "G7s", "平台管理员", "GPS 运维终端清单 2026-07-16",
			"JT808 终端：提供方 未维护→G7s；核验依据：GPS 运维终端清单 2026-07-16",
		).
		WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectCommit()
	mock.ExpectQuery(`SELECT version, updated_by`).
		WithArgs("VIN001").
		WillReturnRows(sqlmock.NewRows([]string{"version", "updated_by", "updated_at"}).AddRow(3, "平台管理员", "2026-07-16 20:00:00"))
	mock.ExpectQuery(`SELECT version, change_type, protocol, source_ref`).
		WithArgs("VIN001", "VIN001").
		WillReturnRows(sqlmock.NewRows([]string{"version", "change_type", "protocol", "source_ref", "source_label", "actor", "changed_at", "summary"}).
			AddRow(3, "provider", "JT808", "a-ref", "G7s", "平台管理员", "2026-07-16 20:00:00", "JT808 终端：提供方 未维护→G7s；核验依据：GPS 运维终端清单 2026-07-16"))

	config, err := store.SaveVehicleSourcePolicy(context.Background(), update)
	if err != nil {
		t.Fatal(err)
	}
	if config.Version != 3 || len(config.Audit) != 1 || config.Audit[0].ChangeType != "provider" {
		t.Fatalf("unexpected provider audit result: %+v", config)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

type canonicalProviderStore struct {
	*MockStore
}

func newCanonicalProviderStore() *canonicalProviderStore {
	return &canonicalProviderStore{MockStore: NewMockStore()}
}

func (s *canonicalProviderStore) VehicleSourceEvidence(_ context.Context, vin string, date string) (VehicleSourceEvidence, error) {
	sourceKey := "JT808:canonical"
	providerKey := vin + "\x00JT808\x00" + sourceKey
	s.sourcePolicyMu.RLock()
	provider := s.sourceProviders[providerKey]
	s.sourcePolicyMu.RUnlock()
	return VehicleSourceEvidence{
		VIN: vin, Plate: "沪A00001", MileageDate: date,
		LocationSources: []VehicleLocationSourceEvidence{{
			Protocol: "JT808", SourceLabel: firstNonEmpty(provider, "JT808"), ProviderOverride: provider,
			SourceKind: "CANONICAL", SelectedWithinProtocol: true, Enabled: true, Priority: 100,
			Online: true, QualityStatus: "OK", sourceKey: sourceKey,
		}},
		MileageSources: []VehicleMileageSourceEvidence{},
	}, nil
}

func TestCanonicalSourceAllowsProviderButRejectsPolicyMutation(t *testing.T) {
	store := newCanonicalProviderStore()
	service := NewService(store)
	admin := WithPrincipal(context.Background(), Principal{Name: "平台管理员", Role: "admin", UserType: "admin"})
	diagnostic, err := service.VehicleSourceDiagnostic(admin, "VIN-CANONICAL-01")
	if err != nil {
		t.Fatal(err)
	}
	if len(diagnostic.Evidence.LocationSources) != 1 || len(diagnostic.Evidence.LocationSources[0].SourceRef) != 64 {
		t.Fatalf("canonical source must expose an opaque provider reference: %+v", diagnostic.Evidence.LocationSources)
	}
	source := diagnostic.Evidence.LocationSources[0]
	updated, err := service.UpdateVehicleSourcePolicy(admin, diagnostic.Evidence.VIN, VehicleSourcePolicyUpdate{
		Version: diagnostic.Policy.Version, SourceRef: source.SourceRef,
		ProviderName: "东方北斗", ProviderEvidence: "GPS 运维终端清单 2026-07-16",
		Enabled: true, Priority: 100,
	})
	if err != nil {
		t.Fatalf("canonical provider update failed: %v", err)
	}
	if updated.Policy.Version != 2 || updated.Evidence.LocationSources[0].ProviderOverride != "东方北斗" {
		t.Fatalf("canonical provider update was not persisted: %+v", updated)
	}
	_, err = service.UpdateVehicleSourcePolicy(admin, diagnostic.Evidence.VIN, VehicleSourcePolicyUpdate{
		Version: 2, SourceRef: source.SourceRef,
		ProviderName: "东方北斗", Enabled: false, Priority: 100,
	})
	clientErr, ok := asClientError(err)
	if !ok || clientErr.Code != "SOURCE_POLICY_CANONICAL_READ_ONLY" {
		t.Fatalf("canonical policy mutation should fail closed, err=%v", err)
	}
}

type accessOnlyProviderStore struct {
	*MockStore
}

func (s *accessOnlyProviderStore) VehicleSourceEvidence(_ context.Context, vin string, date string) (VehicleSourceEvidence, error) {
	return VehicleSourceEvidence{
		VIN: vin, MileageDate: date,
		LocationSources: []VehicleLocationSourceEvidence{},
		MileageSources:  []VehicleMileageSourceEvidence{},
	}, nil
}

func TestDiagnosticSynthesizesProviderTargetForConnectedProtocolWithoutLocation(t *testing.T) {
	service := NewService(&accessOnlyProviderStore{MockStore: NewMockStore()})
	operator := WithPrincipal(context.Background(), Principal{Name: "运维员", Role: "operator", UserType: "operator"})
	diagnostic, err := service.VehicleSourceDiagnostic(operator, "LB9A32A24R0LS1426")
	if err != nil {
		t.Fatal(err)
	}
	var jt808 *VehicleLocationSourceEvidence
	for index := range diagnostic.Evidence.LocationSources {
		if diagnostic.Evidence.LocationSources[index].Protocol == "JT808" {
			jt808 = &diagnostic.Evidence.LocationSources[index]
			break
		}
	}
	if jt808 == nil || jt808.SourceKind != "CANONICAL" || len(jt808.SourceRef) != 64 || jt808.Priority != 100 {
		t.Fatalf("connected protocol without location must remain provider-maintainable: %+v", diagnostic.Evidence.LocationSources)
	}
	if jt808.QualityStatus != "NO_LOCATION" || jt808.QualityReason == "" {
		t.Fatalf("synthetic provider target must explain missing location evidence: %+v", jt808)
	}
}

func TestProductionCanonicalProviderUpdateSkipsTerminalPolicyTable(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	store := NewProductionStore(db, nil, "")
	update := vehicleSourcePolicyStoreUpdate{
		VehicleSourcePolicyUpdate: VehicleSourcePolicyUpdate{
			Version: 1, SourceRef: "canonical-ref", ProviderName: "东方北斗",
			ProviderEvidence: "GPS 运维终端清单 2026-07-16",
			Enabled:          true, Priority: 100, Actor: "平台管理员",
		},
		VIN: "VIN-CANONICAL-01", Protocol: "JT808", SourceKey: "JT808:canonical",
		SourceLabel: "JT808", CurrentEnabled: true, CurrentPriority: 100,
		PolicyConfigurable: false,
	}
	mock.ExpectBegin()
	mock.ExpectExec(regexp.QuoteMeta(`INSERT IGNORE INTO platform_vehicle_source_policy_version
(vin, version, updated_by) VALUES (?, 1, 'system')`)).
		WithArgs("VIN-CANONICAL-01").WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT version FROM platform_vehicle_source_policy_version
WHERE vin = ? FOR UPDATE`)).
		WithArgs("VIN-CANONICAL-01").WillReturnRows(sqlmock.NewRows([]string{"version"}).AddRow(1))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT provider_name
FROM platform_vehicle_source_provider
WHERE vin = ? AND protocol = ? AND source_key = ?`)).
		WithArgs("VIN-CANONICAL-01", "JT808", "JT808:canonical").
		WillReturnRows(sqlmock.NewRows([]string{"provider_name"}))
	mock.ExpectExec(`INSERT INTO platform_vehicle_source_provider`).
		WithArgs("VIN-CANONICAL-01", "JT808", "JT808:canonical", "canonical-ref", "东方北斗", "平台管理员").
		WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectExec(`UPDATE platform_vehicle_source_policy_version`).
		WithArgs(2, "平台管理员", "VIN-CANONICAL-01", 1).
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(`INSERT INTO platform_vehicle_source_provider_audit`).
		WithArgs(
			"VIN-CANONICAL-01", 2, "JT808", "canonical-ref", "JT808:canonical",
			"", "东方北斗", "平台管理员", "GPS 运维终端清单 2026-07-16",
			"JT808：提供方 未维护→东方北斗；核验依据：GPS 运维终端清单 2026-07-16",
		).
		WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectCommit()
	mock.ExpectQuery(`SELECT version, updated_by`).WithArgs("VIN-CANONICAL-01").
		WillReturnRows(sqlmock.NewRows([]string{"version", "updated_by", "updated_at"}).AddRow(2, "平台管理员", "2026-07-16 20:10:00"))
	mock.ExpectQuery(`SELECT version, change_type, protocol, source_ref`).WithArgs("VIN-CANONICAL-01", "VIN-CANONICAL-01").
		WillReturnRows(sqlmock.NewRows([]string{"version", "change_type", "protocol", "source_ref", "source_label", "actor", "changed_at", "summary"}).
			AddRow(2, "provider", "JT808", "canonical-ref", "东方北斗", "平台管理员", "2026-07-16 20:10:00", "JT808：提供方 未维护→东方北斗；核验依据：GPS 运维终端清单 2026-07-16"))

	config, err := store.SaveVehicleSourcePolicy(context.Background(), update)
	if err != nil {
		t.Fatal(err)
	}
	if config.Version != 2 {
		t.Fatalf("unexpected canonical provider config: %+v", config)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}
