package neo4j

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"

	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/encoders/tag"
)

// Import imports events from a reader (JSONL format)
func (n *N) Import(rr io.Reader) {
	n.ImportEventsFromReader(context.Background(), rr)
}

// Export exports events to a writer (JSONL format)
// If pubkeys are provided, only exports events from those authors
// Otherwise exports all events
func (n *N) Export(c context.Context, w io.Writer, pubkeys ...[]byte) {
	var cypher string
	params := make(map[string]any)

	if len(pubkeys) > 0 {
		// Export events for specific pubkeys
		pubkeyStrings := make([]string, len(pubkeys))
		for i, pk := range pubkeys {
			pubkeyStrings[i] = hex.Enc(pk)
		}
		params["pubkeys"] = pubkeyStrings
		cypher = `
MATCH (e:Event)
WHERE e.pubkey IN $pubkeys
RETURN e.id AS id, e.kind AS kind, e.pubkey AS pubkey,
       e.created_at AS created_at, e.content AS content,
       e.sig AS sig, e.tags AS tags
ORDER BY e.created_at ASC`
	} else {
		// Export all events
		cypher = `
MATCH (e:Event)
RETURN e.id AS id, e.kind AS kind, e.pubkey AS pubkey,
       e.created_at AS created_at, e.content AS content,
       e.sig AS sig, e.tags AS tags
ORDER BY e.created_at ASC`
	}

	result, err := n.ExecuteRead(c, cypher, params)
	if err != nil {
		n.Logger.Warningf("failed to query events for export: %v", err)
		fmt.Fprintf(w, "# Export failed: %v\n", err)
		return
	}

	count := 0
	for result.Next(c) {
		record := result.Record()
		if record == nil {
			continue
		}

		// Build event from record
		ev := &event.E{}

		// Parse ID
		if idRaw, found := record.Get("id"); found {
			if idStr, ok := idRaw.(string); ok {
				if idBytes, err := hex.Dec(idStr); err == nil && len(idBytes) == 32 {
					copy(ev.ID[:], idBytes)
				}
			}
		}

		// Parse kind
		if kindRaw, found := record.Get("kind"); found {
			if kindVal, ok := kindRaw.(int64); ok {
				ev.Kind = uint16(kindVal)
			}
		}

		// Parse pubkey
		if pkRaw, found := record.Get("pubkey"); found {
			if pkStr, ok := pkRaw.(string); ok {
				if pkBytes, err := hex.Dec(pkStr); err == nil && len(pkBytes) == 32 {
					copy(ev.Pubkey[:], pkBytes)
				}
			}
		}

		// Parse created_at
		if tsRaw, found := record.Get("created_at"); found {
			if tsVal, ok := tsRaw.(int64); ok {
				ev.CreatedAt = tsVal
			}
		}

		// Parse content
		if contentRaw, found := record.Get("content"); found {
			if contentStr, ok := contentRaw.(string); ok {
				ev.Content = []byte(contentStr)
			}
		}

		// Parse sig
		if sigRaw, found := record.Get("sig"); found {
			if sigStr, ok := sigRaw.(string); ok {
				if sigBytes, err := hex.Dec(sigStr); err == nil && len(sigBytes) == 64 {
					copy(ev.Sig[:], sigBytes)
				}
			}
		}

		// Parse tags (stored as JSON string)
		if tagsRaw, found := record.Get("tags"); found {
			if tagsStr, ok := tagsRaw.(string); ok {
				ev.Tags = &tag.S{}
				if err := json.Unmarshal([]byte(tagsStr), ev.Tags); err != nil {
					n.Logger.Warningf("failed to unmarshal tags: %v", err)
				}
			}
		}

		// Write event as JSON line
		if evJSON, err := json.Marshal(ev); err == nil {
			fmt.Fprintf(w, "%s\n", evJSON)
			count++
		}
	}

	n.Logger.Infof("exported %d events", count)
}

// ImportEventsFromReader imports events from a reader
func (n *N) ImportEventsFromReader(ctx context.Context, rr io.Reader) error {
	scanner := bufio.NewScanner(rr)
	scanner.Buffer(make([]byte, 1024*1024), 10*1024*1024) // 10MB max line size

	count := 0
	for scanner.Scan() {
		line := scanner.Bytes()
		if len(line) == 0 {
			continue
		}

		// Skip comments
		if line[0] == '#' {
			continue
		}

		// Parse event
		ev := &event.E{}
		if err := json.Unmarshal(line, ev); err != nil {
			n.Logger.Warningf("failed to parse event: %v", err)
			continue
		}

		// Save event
		if _, err := n.SaveEvent(ctx, ev); err != nil {
			n.Logger.Warningf("failed to import event: %v", err)
			continue
		}

		count++
		if count%1000 == 0 {
			n.Logger.Infof("imported %d events", count)
		}
	}

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("scanner error: %w", err)
	}

	n.Logger.Infof("import complete: %d events", count)
	return nil
}

// ImportEventsFromStrings imports events from JSON strings
func (n *N) ImportEventsFromStrings(
	ctx context.Context,
	eventJSONs []string,
	policyManager interface{ CheckPolicy(action string, ev *event.E, pubkey []byte, remote string) (bool, error) },
) error {
	for _, eventJSON := range eventJSONs {
		ev := &event.E{}
		if err := json.Unmarshal([]byte(eventJSON), ev); err != nil {
			continue
		}

		// Check policy if manager is provided
		if policyManager != nil {
			if allowed, err := policyManager.CheckPolicy("write", ev, ev.Pubkey[:], "import"); err != nil || !allowed {
				continue
			}
		}

		// Save event
		if _, err := n.SaveEvent(ctx, ev); err != nil {
			n.Logger.Warningf("failed to import event: %v", err)
		}
	}

	return nil
}
