package knowledgeqa

import (
	"net/http"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/forwarder"
)

const (
	knowledgeQACitationMaxLines = 1000
	knowledgeQACitationMaxBytes = 96 << 10
)

func (handler *citationHandler) OpenCitation(request api.WorkspaceScopedRequest) (api.Response, error) {
	sessionID := routeParameter(request.Gin, "sessionId")
	citationID := routeParameter(request.Gin, "citationId")
	if sessionID == "" || citationID == "" {
		return api.Response{}, invalidRequest("sessionId and citationId are required")
	}
	if handler.deps.Citations == nil {
		return api.Response{}, api.NewHTTPErrorWithCode(
			http.StatusServiceUnavailable,
			"Knowledge Q&A citation reader is unavailable",
			"knowledge_qa_runtime_unavailable",
		)
	}
	session, err := handler.deps.Sessions.LoadSessionForOwner(
		request.Context(), request.WorkspaceID(), request.UserID(), sessionID,
	)
	if err != nil {
		return storeFailure(err)
	}
	citation, err := handler.deps.Sessions.LoadCitationForOwner(
		request.Context(), request.WorkspaceID(), request.UserID(), sessionID, citationID,
	)
	if err != nil {
		return storeFailure(err)
	}
	result, err := handler.deps.Citations.OpenKnowledgeQACitation(
		request.Context(),
		forwarder.KnowledgeQACitationOpenRequest{
			Principal: forwarder.KnowledgeQARuntimePrincipal{
				OrganizationID: session.OrganizationID,
				WorkspaceID:    request.WorkspaceID(),
				RequesterID:    request.UserID(),
				TurnID:         citation.TurnID,
			},
			Citation: forwarder.KnowledgeQARuntimeCitation{
				CitationKind:    citation.CitationKind,
				KnowledgeBaseID: citation.KnowledgeBaseID,
				SourceID:        citation.SourceID,
				RootKind:        citation.RootKind,
				RepoRole:        citation.RepoRole,
				Revision:        citation.Revision,
				ResolvedRef:     citation.ResolvedRef,
				ResolvedRefKind: citation.ResolvedRefKind,
				Path:            citation.Path,
				LineStart:       citation.LineStart,
				LineEnd:         citation.LineEnd,
				URL:             citation.URL,
				Title:           citation.Title,
				Snippet:         citation.Snippet,
				FetchedAt:       formatCitationFetchedAt(citation.FetchedAt),
			},
		},
	)
	if err != nil {
		return api.Response{}, err
	}
	response := knowledgeQACitationResponse(citation, result, true)
	httpResponse := api.OK(response)
	httpResponse.Headers = make(http.Header)
	httpResponse.Headers.Set("Cache-Control", "no-store")
	return httpResponse, nil
}

func knowledgeQACitationResponse(
	citation Citation,
	result forwarder.KnowledgeQACitationOpenResult,
	includeSourceEvidence bool,
) dto.KnowledgeQACitationOpenResponse {
	response := dto.KnowledgeQACitationOpenResponse{
		Status:   strings.TrimSpace(result.Status),
		OpenMode: strings.TrimSpace(result.OpenMode),
	}
	if response.Status == "available" {
		if strings.TrimSpace(citation.CitationKind) == "web" {
			response.TargetURL = strings.TrimSpace(result.TargetURL)
			if response.TargetURL == "" {
				response.TargetURL = strings.TrimSpace(citation.URL)
			}
			if response.TargetURL == "" {
				response.Status = "target_unavailable"
				response.OpenMode = ""
			}
		} else {
			switch response.OpenMode {
			case "source":
				if target, ok := knowledgeQACitationTarget(citation); ok {
					response.TargetRoute = target
				} else {
					response.Status = "target_unavailable"
					response.OpenMode = ""
					break
				}
				if includeSourceEvidence && !attachKnowledgeQACitationEvidence(&response, citation, result) {
					response.TargetRoute = ""
				}
			case "evidence":
				attachKnowledgeQACitationEvidence(&response, citation, result)
			default:
				response.Status = "target_unavailable"
				response.OpenMode = ""
			}
		}
	}
	return response
}

func attachKnowledgeQACitationEvidence(
	response *dto.KnowledgeQACitationOpenResponse,
	citation Citation,
	result forwarder.KnowledgeQACitationOpenResult,
) bool {
	content := result.Content
	path := strings.TrimSpace(citation.Path)
	revision := strings.TrimSpace(citation.Revision)
	invalidRange := citation.LineStart < 1 || citation.LineEnd < citation.LineStart
	if !invalidRange {
		invalidRange = citation.LineEnd-citation.LineStart+1 > knowledgeQACitationMaxLines
	}
	if content == "" || len(content) > knowledgeQACitationMaxBytes || path == "" || revision == "" ||
		invalidRange || strings.TrimSpace(result.Path) != path || strings.TrimSpace(result.Revision) != revision ||
		result.LineStart != citation.LineStart || result.LineEnd != citation.LineEnd {
		response.Status = "revision_unavailable"
		response.OpenMode = ""
		response.Evidence = nil
		return false
	}
	response.Evidence = &dto.KnowledgeQACitationEvidence{
		Content: content, Path: path, Revision: revision,
		LineStart: citation.LineStart, LineEnd: citation.LineEnd, Truncated: result.Truncated,
	}
	return true
}

func formatCitationFetchedAt(value *time.Time) string {
	if value == nil {
		return ""
	}
	return value.UTC().Format(time.RFC3339Nano)
}
