package platform

import (
	"context"
	"database/sql"
	"encoding/json"
	"strings"
	"time"
)

const vehicleProfileSelect = `SELECT 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 FROM vehicle_profile `

func (s *ProductionStore) ensureProfileSchema(ctx context.Context) error {
	s.profileSchemaOnce.Do(func() {
		var present int
		s.profileSchemaErr = s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name IN ('vehicle_profile','vehicle_profile_audit')`).Scan(&present)
		if s.profileSchemaErr == nil && present != 2 {
			s.profileSchemaErr = sql.ErrNoRows
		}
	})
	return s.profileSchemaErr
}

func (s *ProductionStore) VehicleProfile(ctx context.Context, vin string) (VehicleProfile, bool, error) {
	if err := s.ensureProfileSchema(ctx); err != nil {
		return VehicleProfile{}, false, err
	}
	profile, err := scanVehicleProfile(s.db.QueryRowContext(ctx, vehicleProfileSelect+`WHERE vin=?`, vin))
	if err == sql.ErrNoRows {
		return VehicleProfile{}, false, nil
	}
	return profile, err == nil, err
}

func (s *ProductionStore) SaveVehicleProfile(ctx context.Context, vin string, input VehicleProfileInput) (VehicleProfile, error) {
	if err := s.ensureProfileSchema(ctx); err != nil {
		return VehicleProfile{}, err
	}
	tx, err := s.db.BeginTx(ctx, &sql.TxOptions{})
	if err != nil {
		return VehicleProfile{}, err
	}
	defer tx.Rollback()
	var current int
	err = tx.QueryRowContext(ctx, `SELECT version FROM vehicle_profile WHERE vin=? FOR UPDATE`, vin).Scan(&current)
	firstAccess := nullableVehicleProfileTime(input.FirstAccessAt)
	if err == sql.ErrNoRows {
		if input.Version != 0 {
			return VehicleProfile{}, clientError{Code: "VEHICLE_PROFILE_NOT_FOUND", Message: "待更新车辆档案不存在"}
		}
		_, err = tx.ExecContext(ctx, `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,?)`, vin, input.ModelName, input.VehicleType, input.CompanyName, input.OperationStatus, input.AccessProvider, firstAccess, input.RuntimeSeconds, input.Actor)
		current = 0
	} else if err != nil {
		return VehicleProfile{}, err
	} else {
		if input.Version != current {
			return VehicleProfile{}, clientError{Code: "VEHICLE_PROFILE_VERSION_CONFLICT", Message: "车辆档案已被其他用户更新，请刷新后重试"}
		}
		result, updateErr := tx.ExecContext(ctx, `UPDATE vehicle_profile SET model_name=?,vehicle_type=?,company_name=?,operation_status=?,access_provider=?,first_access_at=?,runtime_seconds=?,source_system='manual',source_version='',synced_at=NULL,version=version+1,updated_by=? WHERE vin=? AND version=?`, input.ModelName, input.VehicleType, input.CompanyName, input.OperationStatus, input.AccessProvider, firstAccess, input.RuntimeSeconds, input.Actor, vin, current)
		err = updateErr
		if err == nil {
			rows, _ := result.RowsAffected()
			if rows != 1 {
				err = clientError{Code: "VEHICLE_PROFILE_VERSION_CONFLICT", Message: "车辆档案更新冲突，请刷新后重试"}
			}
		}
	}
	if err != nil {
		return VehicleProfile{}, err
	}
	snapshot, _ := json.Marshal(input)
	action := "update"
	if current == 0 {
		action = "create"
	}
	if _, err = tx.ExecContext(ctx, `INSERT INTO vehicle_profile_audit(vin,profile_version,actor,action,snapshot_json) VALUES(?,?,?,?,?)`, vin, current+1, input.Actor, action, string(snapshot)); err != nil {
		return VehicleProfile{}, err
	}
	if err = tx.Commit(); err != nil {
		return VehicleProfile{}, err
	}
	return scanVehicleProfile(s.db.QueryRowContext(ctx, vehicleProfileSelect+`WHERE vin=?`, vin))
}

func (s *ProductionStore) SyncVehicleProfiles(ctx context.Context, request VehicleProfileSyncRequest) (VehicleProfileSyncResult, error) {
	result := VehicleProfileSyncResult{
		SourceSystem: request.SourceSystem, SourceVersion: request.SourceVersion,
		DryRun: request.DryRun, Received: len(request.Items),
		Items: make([]VehicleProfileSyncItemResult, 0, len(request.Items)),
	}
	if err := s.ensureProfileSchema(ctx); err != nil {
		return VehicleProfileSyncResult{}, err
	}
	tx, err := s.db.BeginTx(ctx, &sql.TxOptions{})
	if err != nil {
		return VehicleProfileSyncResult{}, err
	}
	defer tx.Rollback()

	identityArgs := make([]any, 0, len(request.Items))
	for _, item := range request.Items {
		identityArgs = append(identityArgs, item.VIN)
	}
	rows, err := tx.QueryContext(ctx, `SELECT DISTINCT vin FROM vehicle_identity_binding WHERE vin IN (`+repeatPlaceholders(len(identityArgs))+`)`, identityArgs...)
	if err != nil {
		return VehicleProfileSyncResult{}, err
	}
	knownVINs := make(map[string]bool, len(request.Items))
	for rows.Next() {
		var vin string
		if err := rows.Scan(&vin); err != nil {
			rows.Close()
			return VehicleProfileSyncResult{}, err
		}
		knownVINs[vin] = true
	}
	if err := rows.Close(); err != nil {
		return VehicleProfileSyncResult{}, err
	}
	if err := rows.Err(); err != nil {
		return VehicleProfileSyncResult{}, err
	}

	syncedAt := time.Now()
	for _, item := range request.Items {
		itemResult := VehicleProfileSyncItemResult{VIN: item.VIN}
		if !knownVINs[item.VIN] {
			itemResult.Status = profileSyncMissingVehicle
			countVehicleProfileSyncResult(&result, itemResult.Status)
			result.Items = append(result.Items, itemResult)
			continue
		}
		current, scanErr := scanVehicleProfile(tx.QueryRowContext(ctx, vehicleProfileSelect+`WHERE vin=? FOR UPDATE`, item.VIN))
		exists := scanErr == nil
		if scanErr != nil && scanErr != sql.ErrNoRows {
			return VehicleProfileSyncResult{}, scanErr
		}
		if exists {
			itemResult.PreviousSource = current.SourceSystem
			itemResult.PreviousVersion = current.SourceVersion
		}
		itemResult.Status = vehicleProfileSyncDecision(current, exists, request, item)
		itemResult.ProfileVersion = current.Version
		if itemResult.Status == profileSyncCreated {
			itemResult.ProfileVersion = 1
		} else if itemResult.Status == profileSyncUpdated {
			itemResult.ProfileVersion = current.Version + 1
		}
		countVehicleProfileSyncResult(&result, itemResult.Status)
		result.Items = append(result.Items, itemResult)
		if request.DryRun || (itemResult.Status != profileSyncCreated && itemResult.Status != profileSyncUpdated) {
			continue
		}

		firstAccess := nullableVehicleProfileTime(item.FirstAccessAt)
		if itemResult.Status == profileSyncCreated {
			_, err = tx.ExecContext(ctx, `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,?)`, item.VIN, item.ModelName, item.VehicleType, item.CompanyName, item.OperationStatus, item.AccessProvider, firstAccess, item.RuntimeSeconds, request.SourceSystem, request.SourceVersion, syncedAt, request.Actor)
		} else {
			var updateResult sql.Result
			updateResult, err = tx.ExecContext(ctx, `UPDATE vehicle_profile SET model_name=?,vehicle_type=?,company_name=?,operation_status=?,access_provider=?,first_access_at=?,runtime_seconds=?,source_system=?,source_version=?,synced_at=?,version=version+1,updated_by=? WHERE vin=? AND version=?`, item.ModelName, item.VehicleType, item.CompanyName, item.OperationStatus, item.AccessProvider, firstAccess, item.RuntimeSeconds, request.SourceSystem, request.SourceVersion, syncedAt, request.Actor, item.VIN, current.Version)
			if err == nil {
				rowsAffected, rowsErr := updateResult.RowsAffected()
				if rowsErr != nil {
					err = rowsErr
				} else if rowsAffected != 1 {
					err = clientError{Code: "VEHICLE_PROFILE_VERSION_CONFLICT", Message: "车辆档案同步冲突，请重新预演"}
				}
			}
		}
		if err != nil {
			return VehicleProfileSyncResult{}, err
		}
		snapshot, _ := json.Marshal(struct {
			SourceSystem  string                 `json:"sourceSystem"`
			SourceVersion string                 `json:"sourceVersion"`
			Profile       VehicleProfileSyncItem `json:"profile"`
		}{request.SourceSystem, request.SourceVersion, item})
		action := "sync_" + itemResult.Status
		if _, err = tx.ExecContext(ctx, `INSERT INTO vehicle_profile_audit(vin,profile_version,actor,action,snapshot_json) VALUES(?,?,?,?,?)`, item.VIN, itemResult.ProfileVersion, request.Actor, action, string(snapshot)); err != nil {
			return VehicleProfileSyncResult{}, err
		}
	}
	if request.DryRun {
		return result, nil
	}
	if err := tx.Commit(); err != nil {
		return VehicleProfileSyncResult{}, err
	}
	return result, nil
}

func nullableVehicleProfileTime(value string) any {
	if strings.TrimSpace(value) == "" {
		return nil
	}
	parsed, err := parseVehicleProfileTime(value)
	if err != nil {
		return nil
	}
	return parsed
}

type vehicleProfileScanner interface{ Scan(...any) error }

func scanVehicleProfile(scanner vehicleProfileScanner) (VehicleProfile, error) {
	var profile VehicleProfile
	var firstAccess, syncedAt sql.NullTime
	var runtime sql.NullInt64
	var updatedAt time.Time
	err := scanner.Scan(&profile.VIN, &profile.ModelName, &profile.VehicleType, &profile.CompanyName, &profile.OperationStatus, &profile.AccessProvider, &firstAccess, &runtime, &profile.SourceSystem, &profile.SourceVersion, &syncedAt, &profile.Version, &profile.UpdatedBy, &updatedAt)
	if err != nil {
		return VehicleProfile{}, err
	}
	if firstAccess.Valid {
		profile.FirstAccessAt = firstAccess.Time.Format(time.RFC3339)
	}
	if runtime.Valid {
		value := runtime.Int64
		profile.RuntimeSeconds = &value
	}
	if syncedAt.Valid {
		profile.SyncedAt = syncedAt.Time.Format(time.RFC3339)
	}
	profile.UpdatedAt = updatedAt.Format(time.RFC3339)
	return profile, nil
}
