package runtime

import (
	"bytes"
	"context"
	"encoding/base64"
	"fmt"
	"io"
	"path"
	"regexp"
	"sort"
	"strings"
	"unicode/utf8"

	"github.com/tidbcloud/AgentTeam/internal/contentpayload"
	"github.com/tidbcloud/AgentTeam/internal/service/content"
	"github.com/tidbcloud/AgentTeam/internal/store/kb"
)

const (
	contentStoreRepositoryRefPrefix   = "content-store:"
	contentStoreKnowledgeSourceDomain = "kb_source"
	maxKQANativeSourceFileBytes       = 2 << 20
	maxKQAGrepPreviewBytes            = 4 << 10
)

type contentStoreRepositoryAddress struct {
	WorkspaceID      string
	SourceID         string
	ContentID        string
	ContentVersionID string
}

type ContentStoreRepositoryReader interface {
	GetVersion(context.Context, string, string, string) (content.Version, error)
	ListVersionPaths(context.Context, string, string, string, int) ([]content.Path, error)
	OpenFile(context.Context, string, string, string, string, *contentpayload.ByteRange) (io.ReadCloser, contentpayload.BlobMeta, error)
}

type ContentStoreRestrictedEvidenceReader interface {
	OpenFile(context.Context, string, string, string, string, *contentpayload.ByteRange) (io.ReadCloser, contentpayload.BlobMeta, error)
}

type ContentStoreSearchProjection interface {
	ListSourceContentDocumentsForVersion(context.Context, string, string, string, string) ([]kb.SourceSearchDocument, error)
}

// ContentStoreRepositoryPrimitive is the KQA-owned immutable source adapter.
// Root authorization happens once before the opaque address is persisted; tool
// calls only consume that exact ContentVersion and the importer's bounded
// search projection. No request-time Content Store tree scan is used for grep.
type ContentStoreRepositoryPrimitive struct {
	Content ContentStoreRepositoryReader
	// RestrictedEvidence is the raw exact-version reader used only after the
	// share boundary has authorized one bounded citation excerpt. It is never
	// eligible for root exploration, search, or tree listing.
	RestrictedEvidence ContentStoreRestrictedEvidenceReader
	Projection         ContentStoreSearchProjection
}

// RepositoryRouter preserves explicit legacy roots while routing native roots
// solely by their server-only Content Store address. A native error never
// selects the legacy primitive as a fallback.
type RepositoryRouter struct {
	Legacy RepositoryPrimitive
	Native RepositoryPrimitive
}

func NewContentStoreRepositoryRef(workspaceID, sourceID, contentID, contentVersionID string) (string, error) {
	parts := []string{workspaceID, sourceID, contentID, contentVersionID}
	for index := range parts {
		parts[index] = strings.TrimSpace(parts[index])
		if parts[index] == "" || strings.ContainsRune(parts[index], '\x00') {
			return "", fmt.Errorf("Content Store repository identity is invalid")
		}
	}
	return contentStoreRepositoryRefPrefix + base64.RawURLEncoding.EncodeToString([]byte(strings.Join(parts, "\x00"))), nil
}

func (router RepositoryRouter) Grep(ctx context.Context, snapshot RepositorySnapshot, request RepositoryGrepRequest, emit func(RepositoryGrepMatch) error) error {
	primitive, err := router.primitive(snapshot.RepositoryRef)
	if err != nil {
		return err
	}
	return primitive.Grep(ctx, snapshot, request, emit)
}

func (router RepositoryRouter) List(ctx context.Context, snapshot RepositorySnapshot, request RepositoryListRequest, emit func(RepositoryListEntry) error) error {
	primitive, err := router.primitive(snapshot.RepositoryRef)
	if err != nil {
		return err
	}
	return primitive.List(ctx, snapshot, request, emit)
}

func (router RepositoryRouter) Read(ctx context.Context, snapshot RepositorySnapshot, request RepositoryReadRequest, emit func(RepositoryReadChunk) error) error {
	primitive, err := router.primitive(snapshot.RepositoryRef)
	if err != nil {
		return err
	}
	return primitive.Read(ctx, snapshot, request, emit)
}

func (router RepositoryRouter) ResolveRef(ctx context.Context, snapshot RepositorySnapshot, refName, refKind string) (string, error) {
	primitive, err := router.primitive(snapshot.RepositoryRef)
	if err != nil {
		return "", err
	}
	resolver, ok := primitive.(RepositoryRefPrimitive)
	if !ok {
		return "", ErrRepositoryRefInvalid
	}
	return resolver.ResolveRef(ctx, snapshot, refName, refKind)
}

func (router RepositoryRouter) primitive(reference string) (RepositoryPrimitive, error) {
	if strings.HasPrefix(strings.TrimSpace(reference), contentStoreRepositoryRefPrefix) {
		if router.Native == nil {
			return nil, repositoryFailure("native knowledge source content is unavailable", nil)
		}
		return router.Native, nil
	}
	if router.Legacy == nil {
		return nil, repositoryFailure("legacy knowledge source content is unavailable", nil)
	}
	return router.Legacy, nil
}

func (primitive ContentStoreRepositoryPrimitive) Grep(ctx context.Context, snapshot RepositorySnapshot, request RepositoryGrepRequest, emit func(RepositoryGrepMatch) error) error {
	address, err := primitive.resolve(snapshot)
	if err != nil {
		return err
	}
	if snapshot.RestrictedEvidence {
		return ErrRepositoryEscape
	}
	if primitive.Projection == nil {
		return repositoryFailure("native knowledge search projection is unavailable", nil)
	}
	readCtx := contentStoreRepositoryContext(ctx, snapshot, address, content.AccessReadMetadata)
	if _, err := primitive.Content.GetVersion(readCtx, address.WorkspaceID, address.ContentID, address.ContentVersionID); err != nil {
		return err
	}
	expression, err := regexp.CompilePOSIX(strings.TrimSpace(request.Pattern))
	if err != nil {
		return ErrRepositoryEscape
	}
	documents, err := primitive.Projection.ListSourceContentDocumentsForVersion(ctx, address.WorkspaceID, address.SourceID, address.ContentID, address.ContentVersionID)
	if err != nil {
		return err
	}
	for _, document := range documents {
		if err := contextError(ctx); err != nil {
			return err
		}
		if !matchesKQAPathGlobs(document.Path, request.PathGlobs) {
			continue
		}
		lines := strings.Split(document.SearchText, "\n")
		if len(lines) > 0 && lines[len(lines)-1] == "" {
			lines = lines[:len(lines)-1]
		}
		for index, line := range lines {
			if !expression.MatchString(line) {
				continue
			}
			if err := emit(RepositoryGrepMatch{Path: document.Path, Line: index + 1, Preview: truncateKQAPreview(line)}); err != nil {
				return err
			}
		}
	}
	return nil
}

func (primitive ContentStoreRepositoryPrimitive) List(ctx context.Context, snapshot RepositorySnapshot, request RepositoryListRequest, emit func(RepositoryListEntry) error) error {
	address, err := primitive.resolve(snapshot)
	if err != nil {
		return err
	}
	if snapshot.RestrictedEvidence {
		return ErrRepositoryEscape
	}
	readCtx := contentStoreRepositoryContext(ctx, snapshot, address, content.AccessReadPayload)
	paths, err := primitive.Content.ListVersionPaths(readCtx, address.WorkspaceID, address.ContentID, address.ContentVersionID, kb.SourceSearchProjectionMaxDocuments)
	if err != nil {
		return err
	}
	directory := strings.Trim(strings.TrimSpace(request.Path), "/")
	prefix := ""
	if directory != "" {
		prefix = directory + "/"
	}
	entries := make(map[string]string)
	for _, item := range paths {
		itemPath := strings.Trim(strings.TrimSpace(item.Path), "/")
		if !strings.HasPrefix(itemPath, prefix) || itemPath == directory {
			continue
		}
		remainder := strings.TrimPrefix(itemPath, prefix)
		segment, tail, _ := strings.Cut(remainder, "/")
		entryPath := segment
		if directory != "" {
			entryPath = directory + "/" + segment
		}
		kind := "file"
		if tail != "" {
			kind = "directory"
		}
		if previous, exists := entries[entryPath]; !exists || previous == "file" && kind == "directory" {
			entries[entryPath] = kind
		}
	}
	ordered := make([]string, 0, len(entries))
	for entry := range entries {
		ordered = append(ordered, entry)
	}
	sort.Strings(ordered)
	for _, entry := range ordered {
		if err := emit(RepositoryListEntry{Path: entry, Kind: entries[entry]}); err != nil {
			return err
		}
	}
	return nil
}

func (primitive ContentStoreRepositoryPrimitive) Read(ctx context.Context, snapshot RepositorySnapshot, request RepositoryReadRequest, emit func(RepositoryReadChunk) error) error {
	address, err := primitive.resolve(snapshot)
	if err != nil {
		return err
	}
	contentReader := ContentStoreRestrictedEvidenceReader(primitive.Content)
	readCtx := contentStoreRepositoryContext(ctx, snapshot, address, content.AccessReadPayload)
	if snapshot.RestrictedEvidence {
		contentReader = primitive.RestrictedEvidence
		readCtx = ctx
		if contentReader == nil {
			return repositoryFailure("restricted native knowledge evidence is unavailable", nil)
		}
	}
	reader, meta, err := contentReader.OpenFile(readCtx, address.WorkspaceID, address.ContentID, address.ContentVersionID, request.Path, nil)
	if err != nil {
		return err
	}
	defer reader.Close()
	if meta.SizeBytes < 0 || meta.SizeBytes > maxKQANativeSourceFileBytes {
		return ErrRepositoryExhausted
	}
	data, err := io.ReadAll(io.LimitReader(reader, maxKQANativeSourceFileBytes+1))
	if err != nil {
		return err
	}
	if len(data) > maxKQANativeSourceFileBytes {
		return ErrRepositoryExhausted
	}
	if !utf8.Valid(data) || bytes.IndexByte(data, 0) >= 0 {
		return repositoryFailure("native knowledge source file is not text", nil)
	}
	lines := bytes.SplitAfter(data, []byte("\n"))
	if len(lines) > 0 && len(lines[len(lines)-1]) == 0 {
		lines = lines[:len(lines)-1]
	}
	if request.LineStart > len(lines) {
		return nil
	}
	end := request.LineEnd
	if end > len(lines) {
		end = len(lines)
	}
	content := bytes.Join(lines[request.LineStart-1:end], nil)
	return emit(RepositoryReadChunk{Content: content, LineStart: request.LineStart, LineEnd: end})
}

func (primitive ContentStoreRepositoryPrimitive) resolve(snapshot RepositorySnapshot) (contentStoreRepositoryAddress, error) {
	if primitive.Content == nil {
		return contentStoreRepositoryAddress{}, repositoryFailure("native knowledge source content is unavailable", nil)
	}
	encoded := strings.TrimPrefix(strings.TrimSpace(snapshot.RepositoryRef), contentStoreRepositoryRefPrefix)
	if encoded == snapshot.RepositoryRef || encoded == "" {
		return contentStoreRepositoryAddress{}, ErrRepositoryEscape
	}
	raw, err := base64.RawURLEncoding.DecodeString(encoded)
	if err != nil {
		return contentStoreRepositoryAddress{}, ErrRepositoryEscape
	}
	parts := strings.Split(string(raw), "\x00")
	if len(parts) != 4 {
		return contentStoreRepositoryAddress{}, ErrRepositoryEscape
	}
	address := contentStoreRepositoryAddress{WorkspaceID: parts[0], SourceID: parts[1], ContentID: parts[2], ContentVersionID: parts[3]}
	if strings.TrimSpace(snapshot.Revision) != address.ContentVersionID ||
		strings.TrimSpace(snapshot.WorkspaceID) != address.WorkspaceID ||
		strings.TrimSpace(snapshot.SourceID) != address.SourceID || strings.TrimSpace(snapshot.RequesterID) == "" {
		return contentStoreRepositoryAddress{}, ErrRepositoryEscape
	}
	return address, nil
}

func contentStoreRepositoryContext(
	ctx context.Context,
	snapshot RepositorySnapshot,
	address contentStoreRepositoryAddress,
	action content.AccessAction,
) context.Context {
	return content.WithAccessPrincipal(ctx, content.AccessPrincipal{
		Subject: strings.TrimSpace(snapshot.RequesterID), SubjectType: "user",
		WorkspaceID: address.WorkspaceID, Domain: contentStoreKnowledgeSourceDomain,
		BindingKey: address.SourceID, ContentID: address.ContentID, Actions: []content.AccessAction{action},
	})
}

func matchesKQAPathGlobs(candidate string, globs []string) bool {
	if len(globs) == 0 {
		return true
	}
	for _, glob := range globs {
		if matched, _ := path.Match(glob, candidate); matched {
			return true
		}
	}
	return false
}

func truncateKQAPreview(line string) string {
	if len(line) <= maxKQAGrepPreviewBytes {
		return line
	}
	end := maxKQAGrepPreviewBytes
	for end > 0 && !utf8.ValidString(line[:end]) {
		end--
	}
	return line[:end]
}

var _ RepositoryPrimitive = ContentStoreRepositoryPrimitive{}
var _ RepositoryPrimitive = RepositoryRouter{}
var _ RepositoryRefPrimitive = RepositoryRouter{}
