package knowledgeqa

import (
	"fmt"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/forwarder"
	"github.com/tidbcloud/AgentTeam/internal/knowledgeqa/protocol"
	knowledgeqastore "github.com/tidbcloud/AgentTeam/internal/store/knowledgeqa"
)

var runtimeEvidenceIDPattern = regexp.MustCompile(`^E[1-9][0-9]*$`)

type runtimeCitationKey struct {
	citationKind    string
	knowledgeBaseID string
	sourceID        string
	rootKind        string
	repoRole        string
	revision        string
	resolvedRef     string
	resolvedRefKind string
	path            string
	lineStart       int64
	lineEnd         int64
	url             string
	title           string
	snippet         string
	fetchedAt       string
}

func renderRuntimeAnswer(
	sessionID string,
	claims []forwarder.KnowledgeQARuntimeClaim,
	evidence []forwarder.KnowledgeQARuntimeEvidence,
) (string, []knowledgeqastore.Citation, error) {
	sessionID = strings.TrimSpace(sessionID)
	if sessionID == "" || len(claims) == 0 {
		return "", nil, fmt.Errorf("Knowledge Q&A grounded answer is incomplete")
	}

	citations := make([]knowledgeqastore.Citation, 0, len(evidence))
	citationIndexByKey := make(map[runtimeCitationKey]int, len(evidence))
	evidenceIndex := make(map[string]int, len(evidence))
	for _, item := range evidence {
		evidenceID := strings.TrimSpace(item.ID)
		if !runtimeEvidenceIDPattern.MatchString(evidenceID) {
			continue
		}
		citation := runtimeCitation(item.Citation)
		key := runtimeCitationIdentity(citation)
		citationIndex, exists := citationIndexByKey[key]
		if !exists {
			citationIndex = len(citations)
			citationIndexByKey[key] = citationIndex
			citations = append(citations, citation)
		}
		if existing, exists := evidenceIndex[evidenceID]; exists && existing != citationIndex {
			return "", nil, fmt.Errorf("Knowledge Q&A evidence ID is ambiguous")
		}
		evidenceIndex[evidenceID] = citationIndex
	}

	prepared, err := knowledgeqastore.PrepareCitations(citations)
	if err != nil {
		return "", nil, err
	}
	claims = normalizeRuntimeAnswerClaims(claims)
	statements := make([]string, 0, len(claims))
	for _, claim := range claims {
		text := strings.TrimSpace(claim.Text)
		if text == "" {
			continue
		}
		allowed := make(map[string]string, len(claim.EvidenceIDs))
		orderedIDs := make([]string, 0, len(claim.EvidenceIDs))
		for _, evidenceID := range claim.EvidenceIDs {
			evidenceID = strings.TrimSpace(evidenceID)
			citationIndex, exists := evidenceIndex[evidenceID]
			if !exists || citationIndex >= len(prepared) {
				continue
			}
			if _, exists := allowed[evidenceID]; exists {
				continue
			}
			allowed[evidenceID] = knowledgeQACitationHref(sessionID, prepared[citationIndex])
			orderedIDs = append(orderedIDs, evidenceID)
		}
		text, linked := protocol.CanonicalizeEvidenceMarkdown(text, allowed)
		missingLinks := make([]string, 0, len(orderedIDs))
		for _, evidenceID := range orderedIDs {
			if _, exists := linked[evidenceID]; exists {
				continue
			}
			missingLinks = append(missingLinks, "["+evidenceID+"]("+allowed[evidenceID]+")")
		}
		if len(missingLinks) > 0 {
			text += "\n\n" + strings.Join(missingLinks, " ")
		}
		text = protocol.CollapseAdjacentEvidenceLinks(text, allowed)
		statements = append(statements, text)
	}
	answer := strings.Join(statements, "\n\n")
	if strings.TrimSpace(answer) == "" {
		return "", nil, fmt.Errorf("Knowledge Q&A grounded answer is empty")
	}
	return answer, prepared, nil
}

func normalizeRuntimeAnswerClaims(
	claims []forwarder.KnowledgeQARuntimeClaim,
) []forwarder.KnowledgeQARuntimeClaim {
	normalized := make([]forwarder.KnowledgeQARuntimeClaim, 0, len(claims))
	for _, claim := range claims {
		text := strings.TrimSpace(claim.Text)
		if text == "" {
			continue
		}
		if protocol.IsEvidenceOnlyMarkdown(text) {
			if len(normalized) == 0 {
				continue
			}
			previous := &normalized[len(normalized)-1]
			separator := " "
			if strings.Contains(previous.Text, "\n") {
				separator = "\n\n"
			}
			previous.Text = strings.TrimSpace(previous.Text) + separator + text
			previous.EvidenceIDs = append(previous.EvidenceIDs, claim.EvidenceIDs...)
			continue
		}
		normalized = append(normalized, forwarder.KnowledgeQARuntimeClaim{
			Text:        text,
			EvidenceIDs: append([]string(nil), claim.EvidenceIDs...),
		})
	}
	return normalized
}

func runtimeCitation(input forwarder.KnowledgeQARuntimeCitation) knowledgeqastore.Citation {
	return knowledgeqastore.Citation{
		CitationKind:    input.CitationKind,
		KnowledgeBaseID: input.KnowledgeBaseID,
		SourceID:        input.SourceID,
		RootKind:        input.RootKind,
		RepoRole:        input.RepoRole,
		Revision:        input.Revision,
		ResolvedRef:     input.ResolvedRef,
		ResolvedRefKind: input.ResolvedRefKind,
		Path:            input.Path,
		LineStart:       input.LineStart,
		LineEnd:         input.LineEnd,
		URL:             input.URL,
		Title:           input.Title,
		Snippet:         input.Snippet,
		FetchedAt:       parseRuntimeFetchedAt(input.FetchedAt),
	}
}

func runtimeCitationIdentity(citation knowledgeqastore.Citation) runtimeCitationKey {
	fetchedAt := ""
	if citation.FetchedAt != nil {
		fetchedAt = citation.FetchedAt.UTC().Format(time.RFC3339Nano)
	}
	return runtimeCitationKey{
		citationKind:    strings.TrimSpace(citation.CitationKind),
		knowledgeBaseID: strings.TrimSpace(citation.KnowledgeBaseID),
		sourceID:        strings.TrimSpace(citation.SourceID),
		rootKind:        strings.TrimSpace(citation.RootKind),
		repoRole:        strings.TrimSpace(citation.RepoRole),
		revision:        strings.TrimSpace(citation.Revision),
		resolvedRef:     strings.TrimSpace(citation.ResolvedRef),
		resolvedRefKind: strings.TrimSpace(citation.ResolvedRefKind),
		path:            strings.TrimSpace(citation.Path),
		lineStart:       citation.LineStart,
		lineEnd:         citation.LineEnd,
		url:             strings.TrimSpace(citation.URL),
		title:           strings.TrimSpace(citation.Title),
		snippet:         strings.TrimSpace(citation.Snippet),
		fetchedAt:       fetchedAt,
	}
}

func parseRuntimeFetchedAt(raw string) *time.Time {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil
	}
	parsed, err := time.Parse(time.RFC3339Nano, raw)
	if err != nil {
		return nil
	}
	parsed = parsed.UTC()
	return &parsed
}

func knowledgeQACitationHref(sessionID string, citation knowledgeqastore.Citation) string {
	if strings.TrimSpace(citation.CitationKind) == knowledgeqastore.CitationKindWeb {
		if href := knowledgeQAWebCitationHref(citation.URL); href != "" {
			return href
		}
	}
	return "agentteam-kqa-citation://" + sessionID + "/" + strings.TrimSpace(citation.ID)
}

func knowledgeQAWebCitationHref(raw string) string {
	parsed, err := url.Parse(strings.TrimSpace(raw))
	if err != nil || parsed.Host == "" || parsed.User != nil {
		return ""
	}
	if parsed.Scheme != "http" && parsed.Scheme != "https" {
		return ""
	}
	return parsed.String()
}
