package dialectsfcrm

import (
	"context"
	"errors"
	"log"
	"net/http"
	"strings"

	"github.com/nexuscrm/dialect-sf-crm/wire"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/dialect"
	sharederrors "github.com/nexuscrm/shared/pkg/errors"
	crmwire "github.com/pingcap-inc/nexuscrm-app-packs/sfcrm/wire"
)

const recycleBinGenericInternalErrorMessage = "internal server error"

// RecycleBinListNeutralHandler handles GET /recyclebin.
func RecycleBinListNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	recycleBin := deps.Contracts.RecycleBin
	if recycleBin == nil {
		return internalErrorResponse("recycle-bin contract unavailable"), nil
	}

	scope := contract.RecycleBinScope(firstQueryValue(req, "scope"))
	if scope == "" {
		scope = contract.RecycleBinScopeMine
	}
	result, err := recycleBin.ListRecycleBin(ctx, req.User, scope)
	if err != nil {
		return recycleBinErrorResponse(req, err), nil
	}
	if result == nil {
		result = &contract.RecycleBinListResult{}
	}

	items := make([]crmwire.RecycleBinItem, 0, len(result.Items))
	for _, r := range result.Items {
		items = append(items, crmwire.RecycleBinItem{
			ID:               r.ID,
			RecordID:         r.RecordID,
			RecordName:       r.RecordName,
			ObjectAPIName:    r.ObjectAPIName,
			DeletedBy:        r.DeletedBy,
			DeletedDate:      r.DeletedDate,
			CreatedDate:      r.CreatedDate,
			LastModifiedDate: r.LastModifiedDate,
		})
	}

	failed := make([]crmwire.RecycleBinFailure, 0, len(result.FailedObjects))
	for _, f := range result.FailedObjects {
		failed = append(failed, crmwire.RecycleBinFailure{
			ObjectAPIName: f.ObjectAPIName,
			ErrorCode:     f.ErrorCode,
			Message:       f.Message,
		})
	}

	return &dialect.Response{Status: http.StatusOK, Body: crmwire.RecycleBinListResponse{
		Items:          items,
		TotalSize:      len(items),
		PartialFailure: len(failed) > 0,
		FailedObjects:  failed,
	}}, nil
}

// RecycleBinRestoreNeutralHandler handles POST /recyclebin/restore/:id.
func RecycleBinRestoreNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	recycleBin := deps.Contracts.RecycleBin
	if recycleBin == nil {
		return internalErrorResponse("recycle-bin contract unavailable"), nil
	}
	id := strings.TrimSpace(req.Params["id"])
	if id == "" {
		return recycleBinErrorResponse(req, newRecycleBinStatusError(http.StatusBadRequest, "record id is required")), nil
	}
	if err := recycleBin.RestoreRecycleBinRecord(ctx, id, req.User); err != nil {
		return recycleBinErrorResponse(req, err), nil
	}
	return &dialect.Response{Status: http.StatusNoContent}, nil
}

// RecycleBinPurgeNeutralHandler handles DELETE /recyclebin/:id.
func RecycleBinPurgeNeutralHandler(ctx context.Context, req *dialect.Request, deps dialect.Deps) (*dialect.Response, error) {
	recycleBin := deps.Contracts.RecycleBin
	if recycleBin == nil {
		return internalErrorResponse("recycle-bin contract unavailable"), nil
	}
	id := strings.TrimSpace(req.Params["id"])
	if id == "" {
		return recycleBinErrorResponse(req, newRecycleBinStatusError(http.StatusBadRequest, "record id is required")), nil
	}
	if err := recycleBin.PurgeRecycleBinRecord(ctx, id, req.User); err != nil {
		return recycleBinErrorResponse(req, err), nil
	}
	return &dialect.Response{Status: http.StatusNoContent}, nil
}

func recycleBinErrorResponse(req *dialect.Request, err error) *dialect.Response {
	if err == nil {
		return nil
	}
	if !sharederrors.IsInternal(err) {
		if sfErr, status, ok := wire.SFErrorFromDomainForVersion(err, apiVersionOrDefault(req.Version)); ok {
			return &dialect.Response{Status: status, Body: []wire.SFError{sfErr}}
		}
		if sharederrors.IsNotFound(err) {
			return &dialect.Response{Status: http.StatusNotFound, Body: wire.SFErrorResponse(wire.SFCodeNotFound, err.Error())}
		}
		if status, ok := httpStatusFromError(err); ok && status < http.StatusInternalServerError {
			return &dialect.Response{Status: status, Body: wire.SFErrorResponse(recycleBinSFCodeForStatus(status), err.Error())}
		}
	}
	status := http.StatusInternalServerError
	if s, ok := httpStatusFromError(err); ok && s >= http.StatusInternalServerError {
		status = s
	}
	log.Printf("recycle-bin request failed: %v", err)
	return &dialect.Response{Status: status, Body: wire.SFErrorResponse(recycleBinSFCodeForStatus(status), recycleBinGenericInternalErrorMessage)}
}

type recycleBinStatusError struct {
	status  int
	message string
}

func newRecycleBinStatusError(status int, message string) *recycleBinStatusError {
	return &recycleBinStatusError{status: status, message: message}
}

func (e *recycleBinStatusError) Error() string {
	if e == nil {
		return ""
	}
	return e.message
}

func (e *recycleBinStatusError) HTTPStatus() int {
	if e == nil {
		return 0
	}
	return e.status
}

func httpStatusFromError(err error) (int, bool) {
	var statuser httpStatusError
	if !errors.As(err, &statuser) {
		return 0, false
	}
	status := statuser.HTTPStatus()
	if status == 0 {
		return 0, false
	}
	return status, true
}

func recycleBinSFCodeForStatus(status int) string {
	switch status {
	case http.StatusBadRequest:
		return wire.SFCodeInvalidRequest
	case http.StatusForbidden:
		return wire.SFCodeForbidden
	case http.StatusNotFound:
		return wire.SFCodeNotFound
	case http.StatusConflict:
		return wire.SFCodeConflict
	case http.StatusUnauthorized:
		return wire.SFCodeUnauthorized
	default:
		return wire.SFCodeUnknownException
	}
}

func firstQueryValue(req *dialect.Request, key string) string {
	if req == nil || len(req.Query) == 0 {
		return ""
	}
	values := req.Query[key]
	if len(values) == 0 {
		return ""
	}
	return strings.TrimSpace(values[0])
}
