package runtimeapi

import (
	"context"
	"crypto/sha256"
	"fmt"
	"io"
	"path/filepath"

	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	filessemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/filesv1"
	recordsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordsv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
)

type AuthorizedFileEngine interface {
	StoreRecordAttachment(context.Context, contract.FileUploadRequest) (*contract.FileUploadResult, error)
	OpenRecordAttachment(context.Context, string, *models.UserSession) (*contract.FileDownload, bool, error)
	DeleteRecordAttachment(context.Context, string, *models.UserSession) error
}

type AuthorizedFilesServer struct {
	runtimev1.UnimplementedAuthorizedFilesServiceServer
	engine AuthorizedFileEngine
}

func NewAuthorizedFilesServer(engine AuthorizedFileEngine) (*AuthorizedFilesServer, error) {
	if engine == nil {
		return nil, fmt.Errorf("runtime authorized files: engine is required")
	}
	return &AuthorizedFilesServer{engine: engine}, nil
}

func (s *AuthorizedFilesServer) CreateFile(stream runtimev1.AuthorizedFilesService_CreateFileServer) error {
	if stream == nil {
		return mapping.ToPlatformStatusError(
			&recordsemantic.Failure{Category: recordsemantic.FailureInternal},
			filessemantic.CapabilityName,
		)
	}
	ctx := stream.Context()
	scope, ok := runtimeExecutionScopeFromContext(ctx)
	view, viewOK := RuntimeMetadataViewFromContext(ctx)
	trusted, trustedOK := trustedCallFromContext(ctx)
	if !ok || !viewOK || !trustedOK {
		return mapping.ToPlatformStatusError(
			&recordsemantic.Failure{Category: recordsemantic.FailureUnavailable},
			filessemantic.CapabilityName,
		)
	}
	firstWire, err := stream.Recv()
	if err != nil {
		return mapping.ToPlatformStatusError(err, filessemantic.CapabilityName)
	}
	first, err := mapping.FromWireCreateFileChunk(firstWire)
	if err != nil || first.Header == nil {
		return mapping.ToPlatformStatusError(
			&recordsemantic.Failure{Category: recordsemantic.FailureInvalidRequest},
			filessemantic.CapabilityName,
		)
	}
	streamState, err := filessemantic.NewCreateStreamState(ctx)
	if err != nil {
		return mapping.ToPlatformStatusError(err, filessemantic.CapabilityName)
	}
	if err := streamState.Observe(first); err != nil {
		return mapping.ToPlatformStatusError(err, filessemantic.CapabilityName)
	}
	header := *first.Header
	if err := requireCatalogRevision(header.MinCatalogRevision, scope.receipts.CatalogRevision); err != nil {
		return mapping.ToPlatformStatusError(err, filessemantic.CapabilityName)
	}
	if _, found := view.Object(header.ParentObjectID); !found {
		return mapping.ToPlatformStatusError(
			&recordsemantic.Failure{Category: recordsemantic.FailureForbidden},
			filessemantic.CapabilityName,
		)
	}
	reader, writer := io.Pipe()
	stopCancellation := context.AfterFunc(ctx, func() {
		_ = writer.CloseWithError(ctx.Err())
		_ = reader.CloseWithError(ctx.Err())
	})
	defer stopCancellation()
	type uploadOutcome struct {
		result *contract.FileUploadResult
		err    error
	}
	outcome := make(chan uploadOutcome, 1)
	user := trusted.UserSession()
	fileID := deterministicFileID(view.OrgID(), view.AppInstallationID(), header.IdempotencyKey)
	go func() {
		defer reader.Close()
		result, uploadErr := s.engine.StoreRecordAttachment(ctx, contract.FileUploadRequest{
			ID: fileID, FileName: header.FileName, Extension: filepath.Ext(header.FileName),
			ContentType: header.ContentType, Body: reader, ContentLength: int64(header.ContentLength),
			ParentID: header.ParentRecordID, ParentObjectAPIName: header.ParentObjectID,
			AttachmentKind: header.AttachmentKind, User: &user,
		})
		outcome <- uploadOutcome{result: result, err: uploadErr}
	}()
	abortUpload := func(cause error) error {
		_ = writer.CloseWithError(cause)
		uploaded := <-outcome
		if uploaded.err != nil {
			return mapping.ToPlatformStatusError(
				classifyPlatformEngineError(
					ctx, uploaded.err, commonsemantic.InvalidComponentCommandData,
				),
				filessemantic.CapabilityName,
			)
		}
		return mapping.ToPlatformStatusError(cause, filessemantic.CapabilityName)
	}
	earlyEngineCompletion := func(uploaded uploadOutcome) error {
		cause := uploaded.err
		if cause == nil {
			cause = fmt.Errorf("file upload engine completed before the client upload stream")
		}
		_ = writer.CloseWithError(cause)
		return mapping.ToPlatformStatusError(
			classifyPlatformEngineError(ctx, cause, commonsemantic.InvalidComponentCommandData),
			filessemantic.CapabilityName,
		)
	}

	writeChunk := func(data []byte) error {
		if _, writeErr := writer.Write(data); writeErr != nil {
			return writeErr
		}
		return nil
	}
	if err := writeChunk(first.Data); err != nil {
		return abortUpload(err)
	}
	received := clientStreamReceivePump(ctx, stream.Recv)
	for {
		var wireChunk *runtimev1.CreateFileRequest
		var recvErr error
		select {
		case uploaded := <-outcome:
			return earlyEngineCompletion(uploaded)
		case next := <-received:
			wireChunk, recvErr = next.message, next.err
		}
		if recvErr == io.EOF {
			break
		}
		if recvErr != nil {
			return abortUpload(recvErr)
		}
		chunk, mapErr := mapping.FromWireCreateFileChunk(wireChunk)
		if mapErr != nil {
			invalid := &recordsemantic.Failure{Category: recordsemantic.FailureInvalidRequest}
			return abortUpload(invalid)
		}
		if observeErr := streamState.Observe(chunk); observeErr != nil {
			return abortUpload(observeErr)
		}
		if writeErr := writeChunk(chunk.Data); writeErr != nil {
			return abortUpload(writeErr)
		}
	}
	if completeErr := streamState.Complete(); completeErr != nil {
		return abortUpload(completeErr)
	}
	_ = writer.Close()
	uploaded := <-outcome
	if uploaded.err != nil || uploaded.result == nil || uploaded.result.Size < 0 {
		return mapping.ToPlatformStatusError(
			classifyPlatformEngineError(
				ctx, uploaded.err, commonsemantic.InvalidComponentCommandData,
			),
			filessemantic.CapabilityName,
		)
	}
	wireResponse, err := mapping.ToWireCreateFileResponse(filessemantic.CreateResponse{
		FileID: uploaded.result.ID, FileName: uploaded.result.Name, Size: uint64(uploaded.result.Size),
		ContentType: uploaded.result.Mime,
		Receipts:    receiptsForCapability(scope.receipts, filessemantic.CapabilityName),
	})
	if err != nil {
		return mapping.ToPlatformStatusError(err, filessemantic.CapabilityName)
	}
	return stream.SendAndClose(wireResponse)
}

func (s *AuthorizedFilesServer) OpenFile(
	wireRequest *runtimev1.OpenFileRequest,
	stream runtimev1.AuthorizedFilesService_OpenFileServer,
) error {
	if stream == nil {
		return mapping.ToPlatformStatusError(
			&recordsemantic.Failure{Category: recordsemantic.FailureInternal},
			filessemantic.CapabilityName,
		)
	}
	ctx := stream.Context()
	request, err := mapping.FromWireOpenFileRequest(ctx, wireRequest)
	if err != nil {
		return mapping.ToPlatformStatusError(err, filessemantic.CapabilityName)
	}
	scope, ok := runtimeExecutionScopeFromContext(ctx)
	trusted, trustedOK := trustedCallFromContext(ctx)
	if !ok || !trustedOK {
		return mapping.ToPlatformStatusError(
			&recordsemantic.Failure{Category: recordsemantic.FailureUnavailable},
			filessemantic.CapabilityName,
		)
	}
	if err := requireCatalogRevision(request.MinCatalogRevision, scope.receipts.CatalogRevision); err != nil {
		return mapping.ToPlatformStatusError(err, filessemantic.CapabilityName)
	}
	user := trusted.UserSession()
	download, handled, err := s.engine.OpenRecordAttachment(ctx, request.FileID, &user)
	if err != nil {
		return mapping.ToPlatformStatusError(
			classifyPlatformEngineError(
				ctx, err, recordsemantic.InvalidComponentRecordID,
			),
			filessemantic.CapabilityName,
		)
	}
	if !handled || download == nil || download.Reader == nil || download.Size < 0 {
		return mapping.ToPlatformStatusError(
			&recordsemantic.Failure{Category: recordsemantic.FailureMissing},
			filessemantic.CapabilityName,
		)
	}
	defer download.Reader.Close()
	buffer := make([]byte, filessemantic.MaxChunkBytes)
	first := true
	var streamState filessemantic.OpenStreamState
	for {
		count, readErr := download.Reader.Read(buffer)
		if count > 0 || (first && readErr == io.EOF) {
			chunk := filessemantic.OpenChunk{
				FileName: download.FileName, ContentType: download.ContentType,
				TotalSize: uint64(download.Size), Data: append([]byte(nil), buffer[:count]...),
			}
			if first {
				receipts := receiptsForCapability(scope.receipts, filessemantic.CapabilityName)
				chunk.Receipts = &receipts
				first = false
			}
			if observeErr := streamState.Observe(chunk); observeErr != nil {
				return mapping.ToPlatformStatusError(observeErr, filessemantic.CapabilityName)
			}
			wireChunk, mapErr := mapping.ToWireOpenFileChunk(chunk)
			if mapErr != nil {
				return mapping.ToPlatformStatusError(mapErr, filessemantic.CapabilityName)
			}
			if sendErr := stream.Send(wireChunk); sendErr != nil {
				return sendErr
			}
		}
		if readErr == io.EOF {
			if completeErr := streamState.Complete(); completeErr != nil {
				return mapping.ToPlatformStatusError(completeErr, filessemantic.CapabilityName)
			}
			return nil
		}
		if readErr != nil {
			return mapping.ToPlatformStatusError(
				classifyPlatformEngineError(
					ctx, readErr, recordsemantic.InvalidComponentResponse,
				),
				filessemantic.CapabilityName,
			)
		}
	}
}

func (s *AuthorizedFilesServer) DeleteFile(
	ctx context.Context,
	wireRequest *runtimev1.DeleteFileRequest,
) (*runtimev1.DeleteFileResponse, error) {
	request, err := mapping.FromWireDeleteFileRequest(ctx, wireRequest)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, filessemantic.CapabilityName)
	}
	scope, ok := runtimeExecutionScopeFromContext(ctx)
	trusted, trustedOK := trustedCallFromContext(ctx)
	if !ok || !trustedOK {
		return nil, mapping.ToPlatformStatusError(
			&recordsemantic.Failure{Category: recordsemantic.FailureUnavailable},
			filessemantic.CapabilityName,
		)
	}
	if err := requireCatalogRevision(request.MinCatalogRevision, scope.receipts.CatalogRevision); err != nil {
		return nil, mapping.ToPlatformStatusError(err, filessemantic.CapabilityName)
	}
	user := trusted.UserSession()
	if err := s.engine.DeleteRecordAttachment(ctx, request.FileID, &user); err != nil {
		return nil, mapping.ToPlatformStatusError(
			classifyPlatformEngineError(
				ctx, err, recordsemantic.InvalidComponentRecordID,
			),
			filessemantic.CapabilityName,
		)
	}
	wireResponse, err := mapping.ToWireDeleteFileResponse(filessemantic.DeleteResponse{
		Receipts: receiptsForCapability(scope.receipts, filessemantic.CapabilityName),
	})
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, filessemantic.CapabilityName)
	}
	return wireResponse, nil
}

func deterministicFileID(orgID, installationID, idempotencyKey string) string {
	sum := sha256.Sum256([]byte(orgID + "\x00" + installationID + "\x00" + idempotencyKey))
	value := sum[:16]
	value[6] = (value[6] & 0x0f) | 0x50
	value[8] = (value[8] & 0x3f) | 0x80
	return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
		value[0:4], value[4:6], value[6:8], value[8:10], value[10:16],
	)
}

var _ runtimev1.AuthorizedFilesServiceServer = (*AuthorizedFilesServer)(nil)
