package dgraph

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

	"next.orly.dev/pkg/database/indexes/types"
	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/filter"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/encoders/tag"
	"next.orly.dev/pkg/interfaces/store"
)

// QueryEvents retrieves events matching the given filter
func (d *D) QueryEvents(c context.Context, f *filter.F) (evs event.S, err error) {
	return d.QueryEventsWithOptions(c, f, false, false)
}

// QueryAllVersions retrieves all versions of events matching the filter
func (d *D) QueryAllVersions(c context.Context, f *filter.F) (evs event.S, err error) {
	return d.QueryEventsWithOptions(c, f, false, true)
}

// QueryEventsWithOptions retrieves events with specific options
func (d *D) QueryEventsWithOptions(
	c context.Context, f *filter.F, includeDeleteEvents bool, showAllVersions bool,
) (evs event.S, err error) {
	// Build DQL query from Nostr filter
	query := d.buildDQLQuery(f, includeDeleteEvents)

	// Execute query
	resp, err := d.Query(c, query)
	if err != nil {
		return nil, fmt.Errorf("failed to execute query: %w", err)
	}

	// Parse response
	evs, err = d.parseEventsFromResponse(resp.Json)
	if err != nil {
		return nil, fmt.Errorf("failed to parse events: %w", err)
	}

	return evs, nil
}

// buildDQLQuery constructs a DQL query from a Nostr filter
func (d *D) buildDQLQuery(f *filter.F, includeDeleteEvents bool) string {
	return d.buildDQLQueryWithFields(f, includeDeleteEvents, []string{
		"uid",
		"event.id",
		"event.kind",
		"event.created_at",
		"event.content",
		"event.sig",
		"event.pubkey",
		"event.tags",
	})
}

// buildDQLQueryWithFields constructs a DQL query with custom field selection
func (d *D) buildDQLQueryWithFields(f *filter.F, includeDeleteEvents bool, fields []string) string {
	var conditions []string
	var funcQuery string

	// IDs filter
	if len(f.Ids.T) > 0 {
		idConditions := make([]string, len(f.Ids.T))
		for i, id := range f.Ids.T {
			// Handle prefix matching
			if len(id) < 64 {
				// Prefix search
				idConditions[i] = fmt.Sprintf("regexp(event.id, /^%s/)", hex.Enc(id))
			} else {
				idConditions[i] = fmt.Sprintf("eq(event.id, %q)", hex.Enc(id))
			}
		}
		if len(idConditions) == 1 {
			funcQuery = idConditions[0]
		} else {
			conditions = append(conditions, "("+strings.Join(idConditions, " OR ")+")")
		}
	}

	// Authors filter
	if len(f.Authors.T) > 0 {
		authorConditions := make([]string, len(f.Authors.T))
		for i, author := range f.Authors.T {
			// Handle prefix matching
			if len(author) < 64 {
				authorConditions[i] = fmt.Sprintf("regexp(event.pubkey, /^%s/)", hex.Enc(author))
			} else {
				authorConditions[i] = fmt.Sprintf("eq(event.pubkey, %q)", hex.Enc(author))
			}
		}
		if funcQuery == "" && len(authorConditions) == 1 {
			funcQuery = authorConditions[0]
		} else {
			conditions = append(conditions, "("+strings.Join(authorConditions, " OR ")+")")
		}
	}

	// Kinds filter
	if len(f.Kinds.K) > 0 {
		kindConditions := make([]string, len(f.Kinds.K))
		for i, kind := range f.Kinds.K {
			kindConditions[i] = fmt.Sprintf("eq(event.kind, %d)", kind)
		}
		conditions = append(conditions, "("+strings.Join(kindConditions, " OR ")+")")
	}

	// Time range filters
	if f.Since != nil {
		conditions = append(conditions, fmt.Sprintf("ge(event.created_at, %d)", f.Since.V))
	}
	if f.Until != nil {
		conditions = append(conditions, fmt.Sprintf("le(event.created_at, %d)", f.Until.V))
	}

	// Tag filters
	for _, tagValues := range *f.Tags {
		if len(tagValues.T) > 0 {
			tagConditions := make([]string, len(tagValues.T))
			for i, tagValue := range tagValues.T {
				// This is a simplified tag query - in production you'd want to use facets
				tagConditions[i] = fmt.Sprintf("eq(tag.value, %q)", string(tagValue))
			}
			conditions = append(conditions, "("+strings.Join(tagConditions, " OR ")+")")
		}
	}

	// Exclude delete events unless requested
	if !includeDeleteEvents {
		conditions = append(conditions, "NOT eq(event.kind, 5)")
	}

	// Build the final query
	if funcQuery == "" {
		funcQuery = "has(event.id)"
	}

	filterStr := ""
	if len(conditions) > 0 {
		filterStr = " @filter(" + strings.Join(conditions, " AND ") + ")"
	}

	// Add ordering and limit
	orderBy := ", orderdesc: event.created_at"
	limitStr := ""
	if *f.Limit > 0 {
		limitStr = fmt.Sprintf(", first: %d", f.Limit)
	}

	// Build field list
	fieldStr := strings.Join(fields, "\n\t\t\t")

	query := fmt.Sprintf(`{
		events(func: %s%s%s%s) {
			%s
		}
	}`, funcQuery, filterStr, orderBy, limitStr, fieldStr)

	return query
}

// parseEventsFromResponse converts Dgraph JSON response to Nostr events
func (d *D) parseEventsFromResponse(jsonData []byte) ([]*event.E, error) {
	var result struct {
		Events []struct {
			UID       string `json:"uid"`
			ID        string `json:"event.id"`
			Kind      int    `json:"event.kind"`
			CreatedAt int64  `json:"event.created_at"`
			Content   string `json:"event.content"`
			Sig       string `json:"event.sig"`
			Pubkey    string `json:"event.pubkey"`
			Tags      string `json:"event.tags"`
		} `json:"events"`
	}

	if err := json.Unmarshal(jsonData, &result); err != nil {
		return nil, err
	}

	events := make([]*event.E, 0, len(result.Events))
	for _, ev := range result.Events {
		// Decode hex strings
		id, err := hex.Dec(ev.ID)
		if err != nil {
			continue
		}
		sig, err := hex.Dec(ev.Sig)
		if err != nil {
			continue
		}
		pubkey, err := hex.Dec(ev.Pubkey)
		if err != nil {
			continue
		}

		// Parse tags from JSON
		var tags tag.S
		if ev.Tags != "" {
			if err := json.Unmarshal([]byte(ev.Tags), &tags); err != nil {
				continue
			}
		}

		// Create event
		e := &event.E{
			Kind:      uint16(ev.Kind),
			CreatedAt: ev.CreatedAt,
			Content:   []byte(ev.Content),
			Tags:      &tags,
		}

		// Copy fixed-size arrays
		copy(e.ID[:], id)
		copy(e.Sig[:], sig)
		copy(e.Pubkey[:], pubkey)

		events = append(events, e)
	}

	return events, nil
}

// QueryDeleteEventsByTargetId retrieves delete events targeting a specific event ID
func (d *D) QueryDeleteEventsByTargetId(c context.Context, targetEventId []byte) (
	evs event.S, err error,
) {
	targetIDStr := hex.Enc(targetEventId)

	// Query for kind 5 events that reference this event
	query := fmt.Sprintf(`{
		events(func: eq(event.kind, 5)) {
			uid
			event.id
			event.kind
			event.created_at
			event.content
			event.sig
			event.pubkey
			event.tags
			references @filter(eq(event.id, %q)) {
				event.id
			}
		}
	}`, targetIDStr)

	resp, err := d.Query(c, query)
	if err != nil {
		return nil, fmt.Errorf("failed to query delete events: %w", err)
	}

	evs, err = d.parseEventsFromResponse(resp.Json)
	if err != nil {
		return nil, fmt.Errorf("failed to parse delete events: %w", err)
	}

	return evs, nil
}

// QueryForSerials retrieves event serials matching a filter
func (d *D) QueryForSerials(c context.Context, f *filter.F) (
	serials types.Uint40s, err error,
) {
	// Build query requesting only serial numbers
	query := d.buildDQLQueryWithFields(f, false, []string{"event.serial"})

	resp, err := d.Query(c, query)
	if err != nil {
		return nil, fmt.Errorf("failed to query serials: %w", err)
	}

	var result struct {
		Events []struct {
			Serial int64 `json:"event.serial"`
		} `json:"events"`
	}

	if err = json.Unmarshal(resp.Json, &result); err != nil {
		return nil, err
	}

	serials = make([]*types.Uint40, 0, len(result.Events))
	for _, ev := range result.Events {
		serial := types.Uint40{}
		serial.Set(uint64(ev.Serial))
		serials = append(serials, &serial)
	}

	return serials, nil
}

// QueryForIds retrieves event IDs matching a filter
func (d *D) QueryForIds(c context.Context, f *filter.F) (
	idPkTs []*store.IdPkTs, err error,
) {
	// Build query requesting only ID, pubkey, created_at, serial
	query := d.buildDQLQueryWithFields(f, false, []string{
		"event.id",
		"event.pubkey",
		"event.created_at",
		"event.serial",
	})

	resp, err := d.Query(c, query)
	if err != nil {
		return nil, fmt.Errorf("failed to query IDs: %w", err)
	}

	var result struct {
		Events []struct {
			ID        string `json:"event.id"`
			Pubkey    string `json:"event.pubkey"`
			CreatedAt int64  `json:"event.created_at"`
			Serial    int64  `json:"event.serial"`
		} `json:"events"`
	}

	if err = json.Unmarshal(resp.Json, &result); err != nil {
		return nil, err
	}

	idPkTs = make([]*store.IdPkTs, 0, len(result.Events))
	for _, ev := range result.Events {
		id, err := hex.Dec(ev.ID)
		if err != nil {
			continue
		}
		pubkey, err := hex.Dec(ev.Pubkey)
		if err != nil {
			continue
		}
		idPkTs = append(idPkTs, &store.IdPkTs{
			Id:  id,
			Pub: pubkey,
			Ts:  ev.CreatedAt,
			Ser: uint64(ev.Serial),
		})
	}

	return idPkTs, nil
}

// CountEvents counts events matching a filter
func (d *D) CountEvents(c context.Context, f *filter.F) (
	count int, approximate bool, err error,
) {
	// Build query requesting only count
	query := d.buildDQLQueryWithFields(f, false, []string{"count(uid)"})

	resp, err := d.Query(c, query)
	if err != nil {
		return 0, false, fmt.Errorf("failed to count events: %w", err)
	}

	var result struct {
		Events []struct {
			Count int `json:"count"`
		} `json:"events"`
	}

	if err = json.Unmarshal(resp.Json, &result); err != nil {
		return 0, false, err
	}

	if len(result.Events) > 0 {
		count = result.Events[0].Count
	}

	return count, false, nil
}
