package ledger

import (
	"context"
	"errors"
	"strings"

	"github.com/tech/sendico/ledger/storage"
	"github.com/tech/sendico/pkg/api/routers/gsresponse"
	"github.com/tech/sendico/pkg/merrors"
	pmodel "github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mutil/mzap"
	ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
	"go.uber.org/zap"
)

// blockAccountResponder freezes a ledger account, optionally asserting its role first.
func (s *Service) blockAccountResponder(_ context.Context, req *ledgerv1.BlockAccountRequest) gsresponse.Responder[ledgerv1.BlockAccountResponse] {
	return func(ctx context.Context) (*ledgerv1.BlockAccountResponse, error) {
		if s.storage == nil {
			return nil, errStorageNotInitialized
		}
		if req == nil {
			return nil, merrors.InvalidArgument("request is required")
		}
		if strings.TrimSpace(req.LedgerAccountRef) == "" {
			return nil, merrors.InvalidArgument("ledger_account_ref is required")
		}

		accountRef, err := parseObjectID(req.LedgerAccountRef)
		if err != nil {
			return nil, err
		}

		logger := s.logger.With(mzap.AccRef(accountRef))

		account, err := s.storage.Accounts().Get(ctx, accountRef)
		if err != nil {
			if errors.Is(err, storage.ErrAccountNotFound) {
				return nil, merrors.NoData("account not found")
			}
			logger.Warn("Failed to get account for block", zap.Error(err))
			return nil, merrors.Internal("failed to get account")
		}

		// If organization_ref is provided, validate ownership
		if strings.TrimSpace(req.OrganizationRef) != "" {
			orgRef, err := parseObjectID(req.OrganizationRef)
			if err != nil {
				return nil, err
			}
			if *account.OrganizationRef != orgRef {
				return nil, merrors.InvalidArgument("account does not belong to organization")
			}
		}

		// Optional role assertion
		if roleModel, err := protoAccountRoleToModel(req.Role); err == nil && roleModel != "" {
			if err := validateAccountRole(account, roleModel, "account"); err != nil {
				return nil, err
			}
		}

		if account.Status == pmodel.LedgerAccountStatusFrozen {
			logger.Debug("Account already frozen", mzap.AccRef(accountRef))
			return &ledgerv1.BlockAccountResponse{Account: toProtoAccount(account)}, nil
		}

		if err := s.storage.Accounts().UpdateStatus(ctx, accountRef, pmodel.LedgerAccountStatusFrozen); err != nil {
			logger.Warn("Failed to freeze account", zap.Error(err))
			return nil, merrors.Internal("failed to block account")
		}

		account.Status = pmodel.LedgerAccountStatusFrozen
		logger.Info("Account blocked (frozen)", mzap.AccRef(accountRef))
		return &ledgerv1.BlockAccountResponse{Account: toProtoAccount(account)}, nil
	}
}

// unblockAccountResponder unfreezes a ledger account, optionally asserting its role first.
func (s *Service) unblockAccountResponder(_ context.Context, req *ledgerv1.UnblockAccountRequest) gsresponse.Responder[ledgerv1.UnblockAccountResponse] {
	return func(ctx context.Context) (*ledgerv1.UnblockAccountResponse, error) {
		if s.storage == nil {
			return nil, errStorageNotInitialized
		}
		if req == nil {
			return nil, merrors.InvalidArgument("request is required")
		}
		if strings.TrimSpace(req.LedgerAccountRef) == "" {
			return nil, merrors.InvalidArgument("ledger_account_ref is required")
		}

		accountRef, err := parseObjectID(req.LedgerAccountRef)
		if err != nil {
			return nil, err
		}

		logger := s.logger.With(mzap.AccRef(accountRef))

		account, err := s.storage.Accounts().Get(ctx, accountRef)
		if err != nil {
			if errors.Is(err, storage.ErrAccountNotFound) {
				return nil, merrors.NoData("account not found")
			}
			logger.Warn("Failed to get account for unblock", zap.Error(err))
			return nil, merrors.Internal("failed to get account")
		}

		// If organization_ref is provided, validate ownership
		if strings.TrimSpace(req.OrganizationRef) != "" {
			orgRef, err := parseObjectID(req.OrganizationRef)
			if err != nil {
				return nil, err
			}
			if *account.OrganizationRef != orgRef {
				return nil, merrors.InvalidArgument("account does not belong to organization")
			}
		}

		// Optional role assertion
		if roleModel, err := protoAccountRoleToModel(req.Role); err == nil && roleModel != "" {
			if err := validateAccountRole(account, roleModel, "account"); err != nil {
				return nil, err
			}
		}

		if account.Status == pmodel.LedgerAccountStatusActive {
			logger.Debug("Account already active", mzap.AccRef(accountRef))
			return &ledgerv1.UnblockAccountResponse{Account: toProtoAccount(account)}, nil
		}

		if err := s.storage.Accounts().UpdateStatus(ctx, accountRef, pmodel.LedgerAccountStatusActive); err != nil {
			logger.Warn("Failed to activate account", zap.Error(err))
			return nil, merrors.Internal("failed to unblock account")
		}

		account.Status = pmodel.LedgerAccountStatusActive
		logger.Info("Account unblocked (active)", mzap.AccRef(accountRef))
		return &ledgerv1.UnblockAccountResponse{Account: toProtoAccount(account)}, nil
	}
}
