package treasury

import (
	"context"
	"strings"
	"time"

	"github.com/tech/sendico/gateway/chsettle/internal/service/treasury/bot"
	"github.com/tech/sendico/gateway/chsettle/internal/service/treasury/ledger"
	"github.com/tech/sendico/gateway/chsettle/storage"
	storagemodel "github.com/tech/sendico/gateway/chsettle/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/model"
)

type Module struct {
	logger mlogger.Logger

	service   *Service
	router    *bot.Router
	scheduler *Scheduler
	ledger    ledger.Client
}

func NewModule(
	logger mlogger.Logger,
	repo storage.TreasuryRequestsStore,
	users storage.TreasuryTelegramUsersStore,
	ledgerClient ledger.Client,
	cfg Config,
	send bot.SendTextFunc,
) (*Module, error) {
	if logger != nil {
		logger = logger.Named("treasury")
	}
	if users == nil {
		return nil, merrors.InvalidArgument("treasury telegram users store is required", "users")
	}
	service, err := NewService(
		logger,
		repo,
		ledgerClient,
		cfg.ExecutionDelay,
		cfg.MaxAmountPerOperation,
		cfg.MaxDailyAmount,
	)
	if err != nil {
		return nil, err
	}

	module := &Module{
		logger:  logger,
		service: service,
		ledger:  ledgerClient,
	}
	module.scheduler = NewScheduler(logger, service, NotifyFunc(send), cfg.PollInterval)
	module.router = bot.NewRouter(logger, &botServiceAdapter{svc: service}, send, module.scheduler, &botUsersAdapter{store: users})
	return module, nil
}

func (m *Module) Enabled() bool {
	return m != nil && m.router != nil && m.router.Enabled() && m.scheduler != nil
}

func (m *Module) Start() {
	if m == nil || m.scheduler == nil {
		return
	}
	m.scheduler.Start()
}

func (m *Module) Shutdown() {
	if m == nil {
		return
	}
	if m.scheduler != nil {
		m.scheduler.Shutdown()
	}
	if m.ledger != nil {
		_ = m.ledger.Close()
	}
}

func (m *Module) HandleUpdate(ctx context.Context, update *model.TelegramWebhookUpdate) bool {
	if m == nil || m.router == nil {
		return false
	}
	return m.router.HandleUpdate(ctx, update)
}

type botServiceAdapter struct {
	svc *Service
}

type botUsersAdapter struct {
	store storage.TreasuryTelegramUsersStore
}

func (a *botUsersAdapter) ResolveUserBinding(ctx context.Context, telegramUserID string) (*bot.UserBinding, error) {
	if a == nil || a.store == nil {
		return nil, merrors.Internal("treasury users store unavailable")
	}
	record, err := a.store.FindByTelegramUserID(ctx, telegramUserID)
	if err != nil {
		return nil, err
	}
	if record == nil {
		return nil, nil //nolint:nilnil // nil means no treasury user binding configured
	}
	return &bot.UserBinding{
		TelegramUserID:  strings.TrimSpace(record.TelegramUserID),
		LedgerAccountID: strings.TrimSpace(record.LedgerAccountID),
		AllowedChatIDs:  normalizeChatIDs(record.AllowedChatIDs),
	}, nil
}

func (a *botServiceAdapter) ExecutionDelay() (delay time.Duration) {
	if a == nil || a.svc == nil {
		return 0
	}
	return a.svc.ExecutionDelay()
}

func (a *botServiceAdapter) MaxPerOperationLimit() string {
	if a == nil || a.svc == nil {
		return ""
	}
	return a.svc.MaxPerOperationLimit()
}

func (a *botServiceAdapter) GetActiveRequestForAccount(ctx context.Context, ledgerAccountID string) (*storagemodel.TreasuryRequest, error) {
	if a == nil || a.svc == nil {
		return nil, merrors.Internal("treasury service unavailable")
	}
	return a.svc.GetActiveRequestForAccount(ctx, ledgerAccountID)
}

func (a *botServiceAdapter) GetAccountProfile(ctx context.Context, ledgerAccountID string) (*bot.AccountProfile, error) {
	if a == nil || a.svc == nil {
		return nil, merrors.Internal("treasury service unavailable")
	}
	profile, err := a.svc.GetAccountProfile(ctx, ledgerAccountID)
	if err != nil {
		return nil, err
	}
	if profile == nil {
		return nil, nil //nolint:nilnil // nil means no account profile is available
	}
	return &bot.AccountProfile{
		AccountID:   strings.TrimSpace(profile.AccountID),
		AccountCode: strings.TrimSpace(profile.AccountCode),
		Currency:    strings.TrimSpace(profile.Currency),
	}, nil
}

func (a *botServiceAdapter) CreateRequest(ctx context.Context, input bot.CreateRequestInput) (*storagemodel.TreasuryRequest, error) {
	if a == nil || a.svc == nil {
		return nil, merrors.Internal("treasury service unavailable")
	}
	return a.svc.CreateRequest(ctx, CreateRequestInput{
		OperationType:   input.OperationType,
		TelegramUserID:  input.TelegramUserID,
		LedgerAccountID: input.LedgerAccountID,
		ChatID:          input.ChatID,
		Amount:          input.Amount,
	})
}

func (a *botServiceAdapter) ConfirmRequest(ctx context.Context, requestID string, telegramUserID string) (*storagemodel.TreasuryRequest, error) {
	if a == nil || a.svc == nil {
		return nil, merrors.Internal("treasury service unavailable")
	}
	return a.svc.ConfirmRequest(ctx, requestID, telegramUserID)
}

func (a *botServiceAdapter) CancelRequest(ctx context.Context, requestID string, telegramUserID string) (*storagemodel.TreasuryRequest, error) {
	if a == nil || a.svc == nil {
		return nil, merrors.Internal("treasury service unavailable")
	}
	return a.svc.CancelRequest(ctx, requestID, telegramUserID)
}

func normalizeChatIDs(values []string) []string {
	if len(values) == 0 {
		return nil
	}
	out := make([]string, 0, len(values))
	seen := map[string]struct{}{}
	for _, next := range values {
		next = strings.TrimSpace(next)
		if next == "" {
			continue
		}
		if _, ok := seen[next]; ok {
			continue
		}
		seen[next] = struct{}{}
		out = append(out, next)
	}
	if len(out) == 0 {
		return nil
	}
	return out
}
