// Package importer orchestrates daily bar ingestion: it pulls instruments and
// bars from a market data provider for a universe selector and persists them
// through the worker storage ports. It stays decoupled from any concrete
// provider (KIS, etc.) and from the concrete store, so live provider wiring and
// PostgreSQL persistence can be swapped in without touching this flow.
package importer

import (
	"context"
	"fmt"
	"time"

	"git.toki-labs.com/toki/alt/packages/domain/market"
	"git.toki-labs.com/toki/alt/services/worker/internal/storage"
)

// DailyBarRequest is the full import request a job payload describes. It carries
// the provider to fetch from, the universe selector, and an optional inclusive
// date range. The handler resolves it from the job payload so the payload's
// provider/date-range contract reaches the provider instead of being dropped at
// the selector boundary.
type DailyBarRequest struct {
	Provider market.Provider
	Selector market.UniverseSelector
	From     time.Time
	To       time.Time
}

// InstrumentBars couples a resolved instrument with the bars a provider
// returned for it, so the importer can upsert the instrument before its bars.
type InstrumentBars struct {
	Instrument market.Instrument
	Bars       []market.Bar
}

// DailyBarProvider resolves a daily bar request into instruments and their
// daily bars. The KIS provider and any future provider implement this; the
// importer never decodes raw provider payloads itself.
type DailyBarProvider interface {
	FetchDailyBars(ctx context.Context, request DailyBarRequest) ([]InstrumentBars, error)
}

// Stores is the storage surface the importer writes to. It is the worker
// storage ports narrowed to what daily bar import needs.
type Stores interface {
	storage.InstrumentStore
	storage.BarStore
}

// Result reports what an import wrote. Counts cover every upsert issued, so a
// re-import of the same selector reports the same numbers even though the store
// replaces rows in place.
type Result struct {
	Instruments int
	Bars        int
}

// Importer wires a provider to the storage ports.
type Importer struct {
	provider DailyBarProvider
	stores   Stores
}

// New builds an Importer from a provider and the storage ports.
func New(provider DailyBarProvider, stores Stores) *Importer {
	return &Importer{provider: provider, stores: stores}
}

// ImportDailyBars fetches instruments and daily bars for the request and
// upserts them. Upserts are idempotent at the store level: re-importing the
// same request replaces existing instrument/bar rows instead of duplicating
// them.
func (imp *Importer) ImportDailyBars(ctx context.Context, request DailyBarRequest) (Result, error) {
	if imp.provider == nil {
		return Result{}, fmt.Errorf("importer: provider is nil")
	}
	if imp.stores == nil {
		return Result{}, fmt.Errorf("importer: stores are nil")
	}

	fetched, err := imp.provider.FetchDailyBars(ctx, request)
	if err != nil {
		return Result{}, fmt.Errorf("importer: fetch daily bars: %w", err)
	}

	var result Result
	for _, item := range fetched {
		if err := imp.stores.UpsertInstrument(ctx, item.Instrument); err != nil {
			return result, fmt.Errorf("importer: upsert instrument %q: %w", item.Instrument.ID, err)
		}
		result.Instruments++

		for _, bar := range item.Bars {
			if err := imp.stores.UpsertBar(ctx, bar); err != nil {
				return result, fmt.Errorf("importer: upsert bar %q@%s: %w", bar.InstrumentID, bar.Timestamp.Format("2006-01-02"), err)
			}
			result.Bars++
		}
	}
	return result, nil
}
