package platform

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"regexp"
	"testing"
	"time"

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

func TestVehicleProfileReturnsCompletenessAndMissingFields(t *testing.T) {
	service := NewService(NewMockStore())
	profile, err := service.VehicleProfile(context.Background(), "LB9A32A24R0LS1426")
	if err != nil {
		t.Fatal(err)
	}
	if profile.Completeness != 100 || len(profile.MissingFields) != 0 || profile.RuntimeSeconds == nil {
		t.Fatalf("expected complete seeded profile, got %+v", profile)
	}
	empty, err := service.VehicleProfile(context.Background(), "LNXNEGRR7SR318212")
	if err != nil {
		t.Fatal(err)
	}
	if empty.Version != 0 || empty.Completeness != 0 || len(empty.MissingFields) != 7 {
		t.Fatalf("expected explicit empty profile, got %+v", empty)
	}
}

func TestSaveVehicleProfileCreatesAndUsesOptimisticVersion(t *testing.T) {
	service := NewService(NewMockStore())
	runtime := int64(3600)
	input := VehicleProfileInput{ModelName: "氢燃料重卡", VehicleType: "重卡", CompanyName: "示范物流", OperationStatus: "active", AccessProvider: "车厂平台", FirstAccessAt: "2026-07-01T08:30:00+08:00", RuntimeSeconds: &runtime, Actor: "admin-a"}
	profile, err := service.SaveVehicleProfile(context.Background(), "LNXNEGRR7SR318212", input)
	if err != nil {
		t.Fatal(err)
	}
	if profile.Version != 1 || profile.Completeness != 100 || profile.UpdatedBy != "admin-a" {
		t.Fatalf("unexpected created profile: %+v", profile)
	}
	input.Version = 0
	_, err = service.SaveVehicleProfile(context.Background(), "LNXNEGRR7SR318212", input)
	clientErr, ok := asClientError(err)
	if !ok || clientErr.Code != "VEHICLE_PROFILE_VERSION_CONFLICT" {
		t.Fatalf("stale write should conflict, got %v", err)
	}
}

func TestVehicleProfileValidationRejectsInventedVINAndInvalidValues(t *testing.T) {
	service := NewService(NewMockStore())
	negative := int64(-1)
	_, err := service.SaveVehicleProfile(context.Background(), "LNOTPRESENT000000", VehicleProfileInput{OperationStatus: "active", Actor: "admin"})
	clientErr, ok := asClientError(err)
	if !ok || clientErr.Code != "VEHICLE_NOT_FOUND" {
		t.Fatalf("invented VIN should be rejected, got %v", err)
	}
	_, err = service.SaveVehicleProfile(context.Background(), "LNXNEGRR7SR318212", VehicleProfileInput{OperationStatus: "flying", RuntimeSeconds: &negative, Actor: "admin"})
	clientErr, ok = asClientError(err)
	if !ok || clientErr.Code != "VEHICLE_PROFILE_STATUS_INVALID" {
		t.Fatalf("invalid status should be rejected first, got %v", err)
	}
}

func TestVehicleProfileSyncIsDryRunnableIdempotentAndVersionSafe(t *testing.T) {
	service := NewService(NewMockStore())
	runtime := int64(7200)
	request := VehicleProfileSyncRequest{
		SourceSystem: " OEM-TSP ", SourceVersion: "snapshot-20260714-01", DryRun: true, Actor: "sync-admin",
		Items: []VehicleProfileSyncItem{{VIN: " lnxnegrr7sr318212 ", ModelName: "氢燃料重卡", VehicleType: "重卡", CompanyName: "示范物流", OperationStatus: "ACTIVE", AccessProvider: "车厂平台", FirstAccessAt: "2026-07-01T08:30:00+08:00", RuntimeSeconds: &runtime}},
	}
	dryRun, err := service.SyncVehicleProfiles(context.Background(), request)
	if err != nil {
		t.Fatal(err)
	}
	if !dryRun.DryRun || dryRun.Created != 1 || dryRun.Items[0].VIN != "LNXNEGRR7SR318212" {
		t.Fatalf("unexpected dry-run result: %+v", dryRun)
	}
	profile, err := service.VehicleProfile(context.Background(), "LNXNEGRR7SR318212")
	if err != nil || profile.Version != 0 {
		t.Fatalf("dry-run must not persist profile: profile=%+v err=%v", profile, err)
	}

	request.DryRun = false
	created, err := service.SyncVehicleProfiles(context.Background(), request)
	if err != nil || created.Created != 1 {
		t.Fatalf("expected created sync: result=%+v err=%v", created, err)
	}
	profile, err = service.VehicleProfile(context.Background(), "LNXNEGRR7SR318212")
	if err != nil || profile.SourceSystem != "oem-tsp" || profile.SourceVersion != request.SourceVersion || profile.Version != 1 {
		t.Fatalf("unexpected synced profile: %+v err=%v", profile, err)
	}

	repeated, err := service.SyncVehicleProfiles(context.Background(), request)
	if err != nil || repeated.Unchanged != 1 || repeated.Items[0].Status != profileSyncUnchanged {
		t.Fatalf("same source version should be idempotent: result=%+v err=%v", repeated, err)
	}
	request.Items[0].CompanyName = "同版本篡改"
	conflict, err := service.SyncVehicleProfiles(context.Background(), request)
	if err != nil || conflict.Conflicted != 1 || conflict.Items[0].Status != profileSyncConflictSourceVersion {
		t.Fatalf("changed payload under same source version should conflict: result=%+v err=%v", conflict, err)
	}
	request.SourceVersion = "snapshot-20260714-02"
	updated, err := service.SyncVehicleProfiles(context.Background(), request)
	if err != nil || updated.Updated != 1 || updated.Items[0].ProfileVersion != 2 {
		t.Fatalf("new source version should update: result=%+v err=%v", updated, err)
	}
}

func TestVehicleProfileSyncProtectsManualOwnershipAndReportsMissingVIN(t *testing.T) {
	service := NewService(NewMockStore())
	request := VehicleProfileSyncRequest{
		SourceSystem: "fleet-gps", SourceVersion: "42", Actor: "sync-admin",
		Items: []VehicleProfileSyncItem{
			{VIN: "LB9A32A24R0LS1426", ModelName: "外部车型", OperationStatus: "active"},
			{VIN: "LNOTPRESENT000000", ModelName: "未知车辆", OperationStatus: "active"},
		},
	}
	result, err := service.SyncVehicleProfiles(context.Background(), request)
	if err != nil {
		t.Fatal(err)
	}
	if result.Conflicted != 1 || result.Missing != 1 || result.Items[0].Status != profileSyncConflictSource || result.Items[1].Status != profileSyncMissingVehicle {
		t.Fatalf("default policy must preserve manual profile and report missing VIN: %+v", result)
	}
	request.ConflictPolicy = profileSyncConflictPolicyOverwrite
	request.Items = request.Items[:1]
	overwritten, err := service.SyncVehicleProfiles(context.Background(), request)
	if err != nil || overwritten.Updated != 1 {
		t.Fatalf("explicit overwrite should take ownership: result=%+v err=%v", overwritten, err)
	}
	profile, _ := service.VehicleProfile(context.Background(), "LB9A32A24R0LS1426")
	if profile.SourceSystem != "fleet-gps" || profile.ModelName != "外部车型" {
		t.Fatalf("overwrite did not update source ownership: %+v", profile)
	}
}

func TestVehicleProfileSyncValidatesBatchBoundsAndDuplicateVIN(t *testing.T) {
	service := NewService(NewMockStore())
	base := VehicleProfileSyncRequest{SourceSystem: "oem-tsp", SourceVersion: "1", Actor: "admin"}
	base.Items = []VehicleProfileSyncItem{{VIN: "LNXNEGRR7SR318212"}, {VIN: "lnxnegrr7sr318212"}}
	_, err := service.SyncVehicleProfiles(context.Background(), base)
	clientErr, ok := asClientError(err)
	if !ok || clientErr.Code != "VEHICLE_PROFILE_SYNC_DUPLICATE_VIN" {
		t.Fatalf("duplicate VIN should fail validation, got %v", err)
	}
	base.Items = make([]VehicleProfileSyncItem, vehicleProfileSyncLimit+1)
	_, err = service.SyncVehicleProfiles(context.Background(), base)
	clientErr, ok = asClientError(err)
	if !ok || clientErr.Code != "VEHICLE_PROFILE_SYNC_SIZE_INVALID" {
		t.Fatalf("oversized batch should fail validation, got %v", err)
	}
}

func TestVehicleDetailIncludesProfile(t *testing.T) {
	service := NewService(NewMockStore())
	detail, err := service.VehicleDetail(context.Background(), "LB9A32A24R0LS1426", "")
	if err != nil {
		t.Fatal(err)
	}
	if detail.Profile == nil || detail.Profile.CompanyName != "岭牛示范车队" || detail.Profile.Completeness != 100 {
		t.Fatalf("vehicle detail missing master profile: %+v", detail.Profile)
	}
}

func TestVehicleProfileHandlersReadAndUpdate(t *testing.T) {
	handler := NewHandler(NewService(NewMockStore()))
	read := httptest.NewRecorder()
	handler.ServeHTTP(read, httptest.NewRequest(http.MethodGet, "/api/v2/vehicles/LNXNEGRR7SR318212/profile", nil))
	if read.Code != http.StatusOK || !bytes.Contains(read.Body.Bytes(), []byte(`"completeness":0`)) {
		t.Fatalf("empty profile read status=%d body=%s", read.Code, read.Body.String())
	}
	update := httptest.NewRecorder()
	body := bytes.NewBufferString(`{"modelName":"氢燃料重卡","vehicleType":"重卡","companyName":"示范物流","operationStatus":"active","accessProvider":"车厂平台","firstAccessAt":"2026-07-01T08:30","runtimeSeconds":3600,"version":0}`)
	handler.ServeHTTP(update, httptest.NewRequest(http.MethodPut, "/api/v2/vehicles/LNXNEGRR7SR318212/profile", body))
	if update.Code != http.StatusOK {
		t.Fatalf("profile update status=%d body=%s", update.Code, update.Body.String())
	}
	var response struct {
		Data VehicleProfile `json:"data"`
	}
	if err := json.Unmarshal(update.Body.Bytes(), &response); err != nil {
		t.Fatal(err)
	}
	if response.Data.Version != 1 || response.Data.Completeness != 100 || response.Data.UpdatedBy != "system" {
		t.Fatalf("unexpected handler profile: %+v", response.Data)
	}
}

func TestVehicleProfileSyncHandlerUsesAuthenticatedActor(t *testing.T) {
	handler := NewHandler(NewService(NewMockStore()))
	recorder := httptest.NewRecorder()
	body := bytes.NewBufferString(`{"sourceSystem":"oem-tsp","sourceVersion":"v1","items":[{"vin":"LNXNEGRR7SR318212","modelName":"氢燃料重卡","operationStatus":"active"}]}`)
	handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodPost, "/api/v2/vehicle-profiles/sync", body))
	if recorder.Code != http.StatusOK || !bytes.Contains(recorder.Body.Bytes(), []byte(`"created":1`)) {
		t.Fatalf("profile sync status=%d body=%s", recorder.Code, recorder.Body.String())
	}
	profile, _ := handler.service.VehicleProfile(context.Background(), "LNXNEGRR7SR318212")
	if profile.UpdatedBy != "system" {
		t.Fatalf("handler must override body actor with authenticated actor, got %+v", profile)
	}
}

func TestProductionVehicleProfileCreateIsTransactionalAndAudited(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	store := NewProductionStore(db, nil, "")
	now := time.Now()
	runtime := int64(7200)
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name IN ('vehicle_profile','vehicle_profile_audit')`)).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(2))
	mock.ExpectBegin()
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT version FROM vehicle_profile WHERE vin=? FOR UPDATE`)).WithArgs("VIN001").WillReturnRows(sqlmock.NewRows([]string{"version"}))
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO vehicle_profile(vin,model_name,vehicle_type,company_name,operation_status,access_provider,first_access_at,runtime_seconds,source_system,source_version,synced_at,version,updated_by) VALUES(?,?,?,?,?,?,?,?, 'manual','',NULL,1,?)`)).WithArgs("VIN001", "示范车型", "重卡", "示范物流", "active", "车厂平台", sqlmock.AnyArg(), runtime, "admin-a").WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO vehicle_profile_audit(vin,profile_version,actor,action,snapshot_json) VALUES(?,?,?,?,?)`)).WithArgs("VIN001", 1, "admin-a", "create", sqlmock.AnyArg()).WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectCommit()
	columns := []string{"vin", "model_name", "vehicle_type", "company_name", "operation_status", "access_provider", "first_access_at", "runtime_seconds", "source_system", "source_version", "synced_at", "version", "updated_by", "updated_at"}
	mock.ExpectQuery(regexp.QuoteMeta(vehicleProfileSelect + `WHERE vin=?`)).WithArgs("VIN001").WillReturnRows(sqlmock.NewRows(columns).AddRow("VIN001", "示范车型", "重卡", "示范物流", "active", "车厂平台", now, runtime, "manual", "", nil, 1, "admin-a", now))
	profile, err := store.SaveVehicleProfile(context.Background(), "VIN001", VehicleProfileInput{ModelName: "示范车型", VehicleType: "重卡", CompanyName: "示范物流", OperationStatus: "active", AccessProvider: "车厂平台", FirstAccessAt: now.Format(time.RFC3339), RuntimeSeconds: &runtime, Actor: "admin-a"})
	if err != nil {
		t.Fatal(err)
	}
	if profile.Version != 1 || profile.RuntimeSeconds == nil || *profile.RuntimeSeconds != runtime {
		t.Fatalf("unexpected stored profile: %+v", profile)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestProductionVehicleProfileSyncCreatesAndAuditsInOneTransaction(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	store := NewProductionStore(db, nil, "")
	runtime := int64(3600)
	request := VehicleProfileSyncRequest{
		SourceSystem: "oem-tsp", SourceVersion: "snapshot-1", Actor: "sync-admin",
		Items: []VehicleProfileSyncItem{{VIN: "VIN001", ModelName: "示范车型", VehicleType: "重卡", CompanyName: "示范物流", OperationStatus: "active", AccessProvider: "车厂平台", FirstAccessAt: "2026-07-01T08:30:00+08:00", RuntimeSeconds: &runtime}},
	}
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name IN ('vehicle_profile','vehicle_profile_audit')`)).WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(2))
	mock.ExpectBegin()
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT DISTINCT vin FROM vehicle_identity_binding WHERE vin IN (?)`)).WithArgs("VIN001").WillReturnRows(sqlmock.NewRows([]string{"vin"}).AddRow("VIN001"))
	mock.ExpectQuery(regexp.QuoteMeta(vehicleProfileSelect + `WHERE vin=? FOR UPDATE`)).WithArgs("VIN001").WillReturnRows(sqlmock.NewRows([]string{"vin", "model_name", "vehicle_type", "company_name", "operation_status", "access_provider", "first_access_at", "runtime_seconds", "source_system", "source_version", "synced_at", "version", "updated_by", "updated_at"}))
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO vehicle_profile(vin,model_name,vehicle_type,company_name,operation_status,access_provider,first_access_at,runtime_seconds,source_system,source_version,synced_at,version,updated_by) VALUES(?,?,?,?,?,?,?,?,?,?,?,1,?)`)).WithArgs("VIN001", "示范车型", "重卡", "示范物流", "active", "车厂平台", sqlmock.AnyArg(), runtime, "oem-tsp", "snapshot-1", sqlmock.AnyArg(), "sync-admin").WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO vehicle_profile_audit(vin,profile_version,actor,action,snapshot_json) VALUES(?,?,?,?,?)`)).WithArgs("VIN001", 1, "sync-admin", "sync_created", sqlmock.AnyArg()).WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectCommit()
	result, err := store.SyncVehicleProfiles(context.Background(), request)
	if err != nil {
		t.Fatal(err)
	}
	if result.Created != 1 || result.Items[0].ProfileVersion != 1 {
		t.Fatalf("unexpected sync result: %+v", result)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}
