package analytics

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

	pkgerrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
)

// VirtualReportSourceBinding binds one stored report-type identity to an exact
// boot-time virtual source registration. Configuration remains opaque to L0.
type VirtualReportSourceBinding struct {
	SourceRef     string
	Configuration json.RawMessage
}

type virtualReportSourceBindingPayload struct {
	SourceRef     string          `json:"sourceRef"`
	Configuration json.RawMessage `json:"configuration"`
}

// ParseVirtualReportSourceBinding parses the app-blind virtual-source carrier
// in _System_ReportType.Metadata. The bool distinguishes an absent binding from
// a malformed binding, which must fail closed.
func ParseVirtualReportSourceBinding(crt *models.SystemReportType) (VirtualReportSourceBinding, bool, error) {
	if crt == nil {
		return VirtualReportSourceBinding{}, false, nil
	}
	raw := strings.TrimSpace(crt.Metadata)
	if raw == "" || raw == "null" {
		return VirtualReportSourceBinding{}, false, nil
	}
	if !hasVirtualReportSourceBindingCarrier(raw) {
		return VirtualReportSourceBinding{}, false, nil
	}
	payload := map[string]json.RawMessage{}
	if err := json.Unmarshal([]byte(raw), &payload); err != nil {
		return VirtualReportSourceBinding{}, false, fmt.Errorf("analytics: report type %q has invalid metadata JSON: %w", crt.FullName, err)
	}
	virtualSource, exists := payload["virtualSource"]
	if !exists {
		return VirtualReportSourceBinding{}, false, nil
	}
	if joinTemplate, exists := payload["joinTemplate"]; exists && !isNullJSON(joinTemplate) {
		return VirtualReportSourceBinding{}, false, fmt.Errorf("analytics: report type %q cannot declare both joinTemplate and virtualSource", crt.FullName)
	}
	if join := strings.TrimSpace(crt.Join); join != "" && join != "null" {
		return VirtualReportSourceBinding{}, false, fmt.Errorf("analytics: report type %q cannot declare both Join and virtualSource", crt.FullName)
	}

	var binding virtualReportSourceBindingPayload
	decoder := json.NewDecoder(bytes.NewReader(virtualSource))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(&binding); err != nil {
		return VirtualReportSourceBinding{}, false, fmt.Errorf("analytics: report type %q has invalid virtualSource binding: %w", crt.FullName, err)
	}
	if err := requireJSONEOF(decoder); err != nil {
		return VirtualReportSourceBinding{}, false, fmt.Errorf("analytics: report type %q has invalid virtualSource binding: %w", crt.FullName, err)
	}
	binding.SourceRef = strings.TrimSpace(binding.SourceRef)
	if binding.SourceRef == "" {
		return VirtualReportSourceBinding{}, false, fmt.Errorf("analytics: report type %q virtualSource sourceRef is required", crt.FullName)
	}
	if len(binding.Configuration) == 0 {
		binding.Configuration = json.RawMessage(`{}`)
	}
	var configuration map[string]json.RawMessage
	if err := json.Unmarshal(binding.Configuration, &configuration); err != nil || configuration == nil {
		return VirtualReportSourceBinding{}, false, fmt.Errorf("analytics: report type %q virtualSource configuration must be an object", crt.FullName)
	}
	return VirtualReportSourceBinding{
		SourceRef:     binding.SourceRef,
		Configuration: append(json.RawMessage(nil), binding.Configuration...),
	}, true, nil
}

func isNullJSON(raw json.RawMessage) bool {
	return len(raw) == 0 || strings.TrimSpace(string(raw)) == "null"
}

func hasVirtualReportSourceBindingCarrier(metadata string) bool {
	raw := strings.TrimSpace(metadata)
	if raw == "" || raw == "null" {
		return false
	}
	if raw[0] != '{' {
		return false
	}
	delimiters := make([]byte, 0, 4)
	for offset := 0; offset < len(raw); {
		switch raw[offset] {
		case ' ', '\t', '\r', '\n':
			offset++
		case '"':
			start := offset
			offset++
			for offset < len(raw) {
				if raw[offset] == '\\' {
					offset += 2
					continue
				}
				if raw[offset] == '"' {
					offset++
					break
				}
				offset++
			}
			if offset > len(raw) || raw[offset-1] != '"' {
				return false
			}
			if len(delimiters) == 1 {
				next := offset
				for next < len(raw) && strings.ContainsRune(" \t\r\n", rune(raw[next])) {
					next++
				}
				var key string
				if next < len(raw) && raw[next] == ':' && json.Unmarshal([]byte(raw[start:offset]), &key) == nil && key == "virtualSource" {
					return true
				}
			}
		case '{', '[':
			delimiters = append(delimiters, raw[offset])
			offset++
		case '}', ']':
			if len(delimiters) > 0 && matchingJSONDelimiter(delimiters[len(delimiters)-1], raw[offset]) {
				delimiters = delimiters[:len(delimiters)-1]
			}
			if len(delimiters) == 0 {
				return false
			}
			offset++
		default:
			offset++
		}
	}
	return false
}

func matchingJSONDelimiter(open, close byte) bool {
	return open == '{' && close == '}' || open == '[' && close == ']'
}

func requireJSONEOF(decoder *json.Decoder) error {
	var trailing any
	if err := decoder.Decode(&trailing); err == nil {
		return fmt.Errorf("unexpected trailing JSON value")
	} else if err != io.EOF {
		return err
	}
	return nil
}

type virtualReportSourceBindingResolver interface {
	ResolveVirtualReportSourceBinding(ctx context.Context, sourceRef string, user *models.UserSession) (VirtualReportSourceBinding, bool, bool, error)
}

func (r *crtResolver) ResolveVirtualReportSourceBinding(ctx context.Context, sourceRef string, user *models.UserSession) (VirtualReportSourceBinding, bool, bool, error) {
	if r == nil || r.loader == nil {
		return VirtualReportSourceBinding{}, false, false, nil
	}
	crt, err := r.loader.LoadByFullName(ctx, sourceRef, user)
	if err != nil {
		if pkgerrors.IsNotFound(err) {
			return VirtualReportSourceBinding{}, false, false, nil
		}
		return VirtualReportSourceBinding{}, false, false, fmt.Errorf("analytics: resolve virtual report source binding %q: %w", sourceRef, err)
	}
	binding, ok, err := ParseVirtualReportSourceBinding(crt)
	if err != nil || !ok {
		return binding, ok, true, err
	}
	if !strings.EqualFold(binding.SourceRef, strings.TrimSpace(sourceRef)) {
		return VirtualReportSourceBinding{}, false, true, fmt.Errorf("analytics: report type %q binds virtual source %q", sourceRef, binding.SourceRef)
	}
	return binding, true, true, nil
}
