package mapping

import (
	"context"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/bulkingestv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
)

func ToWireCreateBulkIngestJobRequest(ctx context.Context, request semantic.CreateRequest) (*runtimev1.CreateBulkIngestJobRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateCreateRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.CreateBulkIngestJobRequest{ObjectId: request.ObjectID, Operation: toWireBulkOperation(request.Operation), ExternalIdFieldName: request.ExternalIDFieldName, ClientVersion: request.ClientVersion}, nil
}

func FromWireCreateBulkIngestJobRequest(ctx context.Context, request *runtimev1.CreateBulkIngestJobRequest) (semantic.CreateRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.CreateRequest{}, err
	}
	if request == nil {
		return semantic.CreateRequest{}, invalidPlatformWire()
	}
	result := semantic.CreateRequest{ObjectID: request.GetObjectId(), Operation: fromWireBulkOperation(request.GetOperation()), ExternalIDFieldName: request.GetExternalIdFieldName(), ClientVersion: request.GetClientVersion()}
	if err := semantic.ValidateCreateRequest(ctx, result); err != nil {
		return semantic.CreateRequest{}, err
	}
	return result, nil
}

func ToWireReadBulkIngestJobRequest(ctx context.Context, request semantic.ReadRequest) (*runtimev1.ReadBulkIngestJobRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateReadRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.ReadBulkIngestJobRequest{JobId: request.JobID}, nil
}

func FromWireReadBulkIngestJobRequest(ctx context.Context, request *runtimev1.ReadBulkIngestJobRequest) (semantic.ReadRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.ReadRequest{}, err
	}
	if request == nil {
		return semantic.ReadRequest{}, invalidPlatformWire()
	}
	result := semantic.ReadRequest{JobID: request.GetJobId()}
	if err := semantic.ValidateReadRequest(ctx, result); err != nil {
		return semantic.ReadRequest{}, err
	}
	return result, nil
}

func ToWireCloseBulkIngestJobRequest(ctx context.Context, request semantic.CloseRequest) (*runtimev1.CloseBulkIngestJobRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateCloseRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.CloseBulkIngestJobRequest{JobId: request.JobID, State: toWireBulkState(request.State)}, nil
}

func FromWireCloseBulkIngestJobRequest(ctx context.Context, request *runtimev1.CloseBulkIngestJobRequest) (semantic.CloseRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.CloseRequest{}, err
	}
	if request == nil {
		return semantic.CloseRequest{}, invalidPlatformWire()
	}
	result := semantic.CloseRequest{JobID: request.GetJobId(), State: fromWireBulkState(request.GetState())}
	if err := semantic.ValidateCloseRequest(ctx, result); err != nil {
		return semantic.CloseRequest{}, err
	}
	return result, nil
}

func ToWireUploadBulkIngestChunk(chunk semantic.UploadChunk) (*runtimev1.UploadBulkIngestDataRequest, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateUploadChunk(chunk); err != nil {
		return nil, err
	}
	if chunk.Header != nil {
		header := &runtimev1.UploadBulkIngestDataHeader{JobId: chunk.Header.JobID}
		if chunk.Header.ContentLengthKnown {
			header.ContentLength = &chunk.Header.ContentLength
		}
		return &runtimev1.UploadBulkIngestDataRequest{Value: &runtimev1.UploadBulkIngestDataRequest_Header{Header: header}}, nil
	}
	return &runtimev1.UploadBulkIngestDataRequest{Value: &runtimev1.UploadBulkIngestDataRequest_Data{Data: append([]byte(nil), chunk.Data...)}}, nil
}

func FromWireUploadBulkIngestChunk(chunk *runtimev1.UploadBulkIngestDataRequest) (semantic.UploadChunk, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.UploadChunk{}, err
	}
	if chunk == nil {
		return semantic.UploadChunk{}, invalidPlatformWire()
	}
	var result semantic.UploadChunk
	switch value := chunk.GetValue().(type) {
	case *runtimev1.UploadBulkIngestDataRequest_Header:
		if value.Header == nil {
			return semantic.UploadChunk{}, invalidPlatformWire()
		}
		result.Header = &semantic.UploadHeader{JobID: value.Header.GetJobId(), ContentLength: value.Header.GetContentLength(), ContentLengthKnown: value.Header.ContentLength != nil}
	case *runtimev1.UploadBulkIngestDataRequest_Data:
		result.Data = append([]byte(nil), value.Data...)
	default:
		return semantic.UploadChunk{}, invalidPlatformWire()
	}
	if err := semantic.ValidateUploadChunk(result); err != nil {
		return semantic.UploadChunk{}, err
	}
	return result, nil
}

func ToWireCreateBulkIngestJobResponse(request semantic.CreateRequest, response semantic.CreateResponse) (*runtimev1.CreateBulkIngestJobResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateCreateResponse(request, response); err != nil {
		return nil, err
	}
	return &runtimev1.CreateBulkIngestJobResponse{Job: toWireBulkJob(response.Job), Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName))}, nil
}

func FromWireCreateBulkIngestJobResponse(request semantic.CreateRequest, response *runtimev1.CreateBulkIngestJobResponse) (semantic.CreateResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.CreateResponse{}, err
	}
	if response == nil || response.GetJob() == nil || response.GetReceipts() == nil || !validWireBulkFailureCode(response.GetJob().GetFailureCode()) {
		return semantic.CreateResponse{}, incompatibleFailureFor(platformCapability(semantic.CapabilityName))
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.CreateResponse{}, err
	}
	result := semantic.CreateResponse{Job: fromWireBulkJob(response.GetJob()), Receipts: receipts}
	if err := semantic.ValidateCreateResponse(request, result); err != nil {
		return semantic.CreateResponse{}, err
	}
	return result, nil
}

func ToWireReadBulkIngestJobResponse(request semantic.ReadRequest, response semantic.ReadResponse) (*runtimev1.ReadBulkIngestJobResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateReadResponse(request, response); err != nil {
		return nil, err
	}
	return &runtimev1.ReadBulkIngestJobResponse{Job: toWireBulkJob(response.Job), Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName))}, nil
}

func FromWireReadBulkIngestJobResponse(request semantic.ReadRequest, response *runtimev1.ReadBulkIngestJobResponse) (semantic.ReadResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.ReadResponse{}, err
	}
	if response == nil || response.GetJob() == nil || response.GetReceipts() == nil || !validWireBulkFailureCode(response.GetJob().GetFailureCode()) {
		return semantic.ReadResponse{}, incompatibleFailureFor(platformCapability(semantic.CapabilityName))
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.ReadResponse{}, err
	}
	result := semantic.ReadResponse{Job: fromWireBulkJob(response.GetJob()), Receipts: receipts}
	if err := semantic.ValidateReadResponse(request, result); err != nil {
		return semantic.ReadResponse{}, err
	}
	return result, nil
}

func ToWireCloseBulkIngestJobResponse(request semantic.CloseRequest, response semantic.CloseResponse) (*runtimev1.CloseBulkIngestJobResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateCloseResponse(request, response); err != nil {
		return nil, err
	}
	return &runtimev1.CloseBulkIngestJobResponse{Job: toWireBulkJob(response.Job), Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName))}, nil
}

func FromWireCloseBulkIngestJobResponse(request semantic.CloseRequest, response *runtimev1.CloseBulkIngestJobResponse) (semantic.CloseResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.CloseResponse{}, err
	}
	if response == nil || response.GetJob() == nil || response.GetReceipts() == nil || !validWireBulkFailureCode(response.GetJob().GetFailureCode()) {
		return semantic.CloseResponse{}, incompatibleFailureFor(platformCapability(semantic.CapabilityName))
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.CloseResponse{}, err
	}
	result := semantic.CloseResponse{Job: fromWireBulkJob(response.GetJob()), Receipts: receipts}
	if err := semantic.ValidateCloseResponse(request, result); err != nil {
		return semantic.CloseResponse{}, err
	}
	return result, nil
}

func ToWireUploadBulkIngestResponse(request semantic.UploadHeader, response semantic.UploadResponse) (*runtimev1.UploadBulkIngestDataResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return nil, err
	}
	if err := semantic.ValidateUploadResponse(request, response); err != nil {
		return nil, err
	}
	return &runtimev1.UploadBulkIngestDataResponse{JobId: response.JobID, Receipts: toWireReceipts(response.Receipts, platformCapability(semantic.CapabilityName))}, nil
}

func FromWireUploadBulkIngestResponse(request semantic.UploadHeader, response *runtimev1.UploadBulkIngestDataResponse) (semantic.UploadResponse, error) {
	if err := requirePlatformSemanticShape(semantic.CapabilityName); err != nil {
		return semantic.UploadResponse{}, err
	}
	if response == nil || response.GetReceipts() == nil {
		return semantic.UploadResponse{}, incompatibleFailureFor(platformCapability(semantic.CapabilityName))
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), platformCapability(semantic.CapabilityName))
	if err != nil {
		return semantic.UploadResponse{}, err
	}
	result := semantic.UploadResponse{JobID: response.GetJobId(), Receipts: receipts}
	if err := semantic.ValidateUploadResponse(request, result); err != nil {
		return semantic.UploadResponse{}, err
	}
	return result, nil
}

func toWireBulkJob(job semantic.Job) *runtimev1.BulkIngestJob {
	return &runtimev1.BulkIngestJob{Id: job.ID, ObjectId: job.ObjectID, Operation: toWireBulkOperation(job.Operation), State: toWireBulkState(job.State), CreatedById: job.CreatedByID, CreatedAt: job.CreatedAt, ModifiedAt: job.ModifiedAt, FailureCode: toWireBulkFailureCode(job.FailureCode), ExternalIdFieldName: job.ExternalIDFieldName, NumberRecordsProcessed: job.NumberRecordsProcessed, NumberRecordsFailed: job.NumberRecordsFailed, TotalProcessingTime: job.TotalProcessingTime, ActiveProcessingTime: job.ActiveProcessingTime, ClientVersion: job.ClientVersion}
}

func fromWireBulkJob(job *runtimev1.BulkIngestJob) semantic.Job {
	return semantic.Job{ID: job.GetId(), ObjectID: job.GetObjectId(), Operation: fromWireBulkOperation(job.GetOperation()), State: fromWireBulkState(job.GetState()), CreatedByID: job.GetCreatedById(), CreatedAt: job.GetCreatedAt(), ModifiedAt: job.GetModifiedAt(), FailureCode: fromWireBulkFailureCode(job.GetFailureCode()), ExternalIDFieldName: job.GetExternalIdFieldName(), NumberRecordsProcessed: job.GetNumberRecordsProcessed(), NumberRecordsFailed: job.GetNumberRecordsFailed(), TotalProcessingTime: job.GetTotalProcessingTime(), ActiveProcessingTime: job.GetActiveProcessingTime(), ClientVersion: job.GetClientVersion()}
}

func toWireBulkFailureCode(code semantic.FailureCode) runtimev1.BulkIngestFailureCode {
	if code == semantic.FailureProcessingFailed {
		return runtimev1.BulkIngestFailureCode_BULK_INGEST_FAILURE_CODE_PROCESSING_FAILED
	}
	return runtimev1.BulkIngestFailureCode_BULK_INGEST_FAILURE_CODE_UNSPECIFIED
}

func fromWireBulkFailureCode(code runtimev1.BulkIngestFailureCode) semantic.FailureCode {
	if code == runtimev1.BulkIngestFailureCode_BULK_INGEST_FAILURE_CODE_PROCESSING_FAILED {
		return semantic.FailureProcessingFailed
	}
	return ""
}

func validWireBulkFailureCode(code runtimev1.BulkIngestFailureCode) bool {
	return code == runtimev1.BulkIngestFailureCode_BULK_INGEST_FAILURE_CODE_UNSPECIFIED ||
		code == runtimev1.BulkIngestFailureCode_BULK_INGEST_FAILURE_CODE_PROCESSING_FAILED
}

func toWireBulkOperation(operation semantic.Operation) runtimev1.BulkIngestOperation {
	switch operation {
	case semantic.OperationInsert:
		return runtimev1.BulkIngestOperation_BULK_INGEST_OPERATION_INSERT
	case semantic.OperationUpdate:
		return runtimev1.BulkIngestOperation_BULK_INGEST_OPERATION_UPDATE
	case semantic.OperationUpsert:
		return runtimev1.BulkIngestOperation_BULK_INGEST_OPERATION_UPSERT
	case semantic.OperationDelete:
		return runtimev1.BulkIngestOperation_BULK_INGEST_OPERATION_DELETE
	default:
		return runtimev1.BulkIngestOperation_BULK_INGEST_OPERATION_UNSPECIFIED
	}
}

func fromWireBulkOperation(operation runtimev1.BulkIngestOperation) semantic.Operation {
	switch operation {
	case runtimev1.BulkIngestOperation_BULK_INGEST_OPERATION_INSERT:
		return semantic.OperationInsert
	case runtimev1.BulkIngestOperation_BULK_INGEST_OPERATION_UPDATE:
		return semantic.OperationUpdate
	case runtimev1.BulkIngestOperation_BULK_INGEST_OPERATION_UPSERT:
		return semantic.OperationUpsert
	case runtimev1.BulkIngestOperation_BULK_INGEST_OPERATION_DELETE:
		return semantic.OperationDelete
	default:
		return ""
	}
}

func toWireBulkState(state semantic.State) runtimev1.BulkIngestState {
	switch state {
	case semantic.StateOpen:
		return runtimev1.BulkIngestState_BULK_INGEST_STATE_OPEN
	case semantic.StateUploadComplete:
		return runtimev1.BulkIngestState_BULK_INGEST_STATE_UPLOAD_COMPLETE
	case semantic.StateInProgress:
		return runtimev1.BulkIngestState_BULK_INGEST_STATE_IN_PROGRESS
	case semantic.StateComplete:
		return runtimev1.BulkIngestState_BULK_INGEST_STATE_COMPLETE
	case semantic.StateFailed:
		return runtimev1.BulkIngestState_BULK_INGEST_STATE_FAILED
	case semantic.StateAborted:
		return runtimev1.BulkIngestState_BULK_INGEST_STATE_ABORTED
	default:
		return runtimev1.BulkIngestState_BULK_INGEST_STATE_UNSPECIFIED
	}
}

func fromWireBulkState(state runtimev1.BulkIngestState) semantic.State {
	switch state {
	case runtimev1.BulkIngestState_BULK_INGEST_STATE_OPEN:
		return semantic.StateOpen
	case runtimev1.BulkIngestState_BULK_INGEST_STATE_UPLOAD_COMPLETE:
		return semantic.StateUploadComplete
	case runtimev1.BulkIngestState_BULK_INGEST_STATE_IN_PROGRESS:
		return semantic.StateInProgress
	case runtimev1.BulkIngestState_BULK_INGEST_STATE_COMPLETE:
		return semantic.StateComplete
	case runtimev1.BulkIngestState_BULK_INGEST_STATE_FAILED:
		return semantic.StateFailed
	case runtimev1.BulkIngestState_BULK_INGEST_STATE_ABORTED:
		return semantic.StateAborted
	default:
		return ""
	}
}
