package jobs

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

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

// importDateLayout matches the KIS YYYYMMDD date fields the payload date range
// uses (see the KIS daily chart request fixture).
const importDateLayout = "20060102"

// DailyBarImporter is the import surface a daily bar job dispatches to. The
// jobs package depends only on this interface so the concrete importer (and its
// provider/storage wiring) stays out of the job boundary.
type DailyBarImporter interface {
	ImportDailyBars(ctx context.Context, request importer.DailyBarRequest) (importer.Result, error)
}

// DailyBarImportPayload is the decoded KindImportDailyBars job payload. It
// carries the provider and the universe selector (kind, venue/market/name, and
// symbols) plus an optional date range the provider applies when fetching.
type DailyBarImportPayload struct {
	Provider     string   `json:"provider"`
	SelectorKind string   `json:"selector_kind"`
	Market       string   `json:"market"`
	Venue        string   `json:"venue"`
	Name         string   `json:"name"`
	Symbols      []string `json:"symbols"`
	From         string   `json:"from,omitempty"`
	To           string   `json:"to,omitempty"`
}

// selector builds the domain universe selector this payload describes.
func (p DailyBarImportPayload) selector() market.UniverseSelector {
	return market.UniverseSelector{
		Kind:    market.UniverseSelectorKind(p.SelectorKind),
		Market:  market.Market(p.Market),
		Venue:   market.Venue(p.Venue),
		Symbols: p.Symbols,
		Name:    p.Name,
	}
}

// request resolves the payload into an importer request, parsing the optional
// YYYYMMDD date range so the provider/date-range contract reaches the importer.
func (p DailyBarImportPayload) request() (importer.DailyBarRequest, error) {
	req := importer.DailyBarRequest{
		Provider: market.Provider(p.Provider),
		Selector: p.selector(),
	}
	from, err := parseImportDate("from", p.From)
	if err != nil {
		return importer.DailyBarRequest{}, err
	}
	to, err := parseImportDate("to", p.To)
	if err != nil {
		return importer.DailyBarRequest{}, err
	}
	req.From = from
	req.To = to
	return req, nil
}

// parseImportDate parses an optional YYYYMMDD date. An empty value yields the
// zero time; a malformed value is an error.
func parseImportDate(field, value string) (time.Time, error) {
	if value == "" {
		return time.Time{}, nil
	}
	t, err := time.Parse(importDateLayout, value)
	if err != nil {
		return time.Time{}, fmt.Errorf("daily bar import payload: invalid %s date %q: %w", field, value, err)
	}
	return t, nil
}

// DecodeDailyBarImportPayload decodes and validates a KindImportDailyBars
// payload.
func DecodeDailyBarImportPayload(raw json.RawMessage) (DailyBarImportPayload, error) {
	var p DailyBarImportPayload
	if err := json.Unmarshal(raw, &p); err != nil {
		return DailyBarImportPayload{}, fmt.Errorf("decode daily bar import payload: %w", err)
	}
	if p.Provider == "" {
		return DailyBarImportPayload{}, fmt.Errorf("daily bar import payload: provider is required")
	}
	if p.SelectorKind == "" {
		return DailyBarImportPayload{}, fmt.Errorf("daily bar import payload: selector_kind is required")
	}
	if len(p.Symbols) == 0 {
		return DailyBarImportPayload{}, fmt.Errorf("daily bar import payload: symbols is required")
	}
	return p, nil
}

// RegisterDailyBarImportHandler registers the real KindImportDailyBars handler,
// replacing the built-in placeholder. It decodes the payload into a daily bar
// request, gates it through the provider capability (rejecting provider
// mismatches and unsupported market/venue/daily-bar combinations before any
// fetch), and dispatches to the importer. RegisterBuiltins still registers all
// placeholders first; callers invoke this afterwards to wire the live importer.
func RegisterDailyBarImportHandler(runner *Runner, capability market.ProviderCapability, imp DailyBarImporter) {
	runner.Register(KindImportDailyBars, func(ctx context.Context, payload json.RawMessage) error {
		p, err := DecodeDailyBarImportPayload(payload)
		if err != nil {
			return err
		}
		if err := capability.RequireDailyBars(market.Provider(p.Provider), p.selector()); err != nil {
			return fmt.Errorf("daily bar import payload: %w", err)
		}
		req, err := p.request()
		if err != nil {
			return err
		}
		if _, err := imp.ImportDailyBars(ctx, req); err != nil {
			return fmt.Errorf("import daily bars job: %w", err)
		}
		return nil
	})
}
