package knowledgeqa

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

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

// Create handles POST /api/v1/workspaces/:workspaceId/knowledge-qa/sessions/:sessionId/turns/:turnId/share.
// It creates or rotates an invitation for one completed answer owned by the caller.
func (handler *shareHandler) Create(
	r api.WorkspaceScopedRequest,
) (api.Response, error) {
	sessionID := routeParameter(r.Gin, "sessionId")
	turnID := routeParameter(r.Gin, "turnId")
	if sessionID == "" || turnID == "" {
		return api.Response{}, invalidRequest("sessionId and turnId are required")
	}
	if handler.deps.Sharing == nil {
		return api.Response{}, errors.New("Knowledge Q&A sharing service is unavailable")
	}
	shareURL, err := knowledgeQAShareEntryURL(handler.deps.PublicBaseURL)
	if err != nil {
		return api.Response{}, err
	}
	share, err := handler.deps.Sharing.CreateShare(
		r.Context(), r.WorkspaceID(), r.UserID(), sessionID,
		turnID, time.Now().UTC(),
	)
	if err != nil {
		return storeFailure(err)
	}
	shareURL.Fragment = share.Token
	response := api.Created(dto.KnowledgeQAShareCreateResponse{
		ShareID: share.ID,
		URL:     shareURL.String(),
	})
	response.Headers = privateShareHeaders()
	return response, nil
}

func knowledgeQAShareEntryURL(publicBaseURL string) (*url.URL, error) {
	base, err := url.Parse(strings.TrimSpace(publicBaseURL))
	if err != nil || base.Scheme == "" || base.Host == "" || base.User != nil ||
		(base.Scheme != "http" && base.Scheme != "https") {
		return nil, errors.New("Knowledge Q&A public share URL is unavailable")
	}
	base.Path = strings.TrimRight(base.Path, "/") + "/kb/shared"
	base.RawPath = ""
	base.RawQuery = ""
	base.Fragment = ""
	return base, nil
}

// Open handles POST /api/v1/workspaces/:workspaceId/knowledge-qa/shares/open.
// It redeems one invitation for the authenticated Human; the route workspace is
// a transport anchor and does not grant access to the source workspace.
func (handler *shareHandler) Open(
	r api.WorkspaceScopedJSON[dto.KnowledgeQAShareOpenRequest],
) (api.Response, error) {
	if handler.deps.Sharing == nil {
		return api.Response{}, errors.New("Knowledge Q&A sharing service is unavailable")
	}
	share, err := handler.deps.Sharing.OpenShare(
		r.Context(), r.UserID(), r.Body.Token, time.Now().UTC(),
	)
	if err != nil {
		return storeFailure(err)
	}
	return privateShareResponse(dto.KnowledgeQAShareResponse{
		Share: toShareDTO(share),
	}), nil
}

// Get handles GET /api/v1/workspaces/:workspaceId/knowledge-qa/shares/:shareId.
// It returns one share already redeemed by the authenticated Human.
func (handler *shareHandler) Get(r api.WorkspaceScopedRequest) (api.Response, error) {
	shareID := routeParameter(r.Gin, "shareId")
	if !strings.HasPrefix(shareID, "kqh_") {
		return api.Response{}, api.ErrNotFound
	}
	if handler.deps.Sharing == nil {
		return api.Response{}, errors.New("Knowledge Q&A sharing service is unavailable")
	}
	share, err := handler.deps.Sharing.LoadReceivedShare(
		r.Context(), r.UserID(), shareID, time.Now().UTC(),
	)
	if err != nil {
		return storeFailure(err)
	}
	return privateShareResponse(dto.KnowledgeQAShareResponse{
		Share: toShareDTO(share),
	}), nil
}

// OpenEvidence handles GET /api/v1/workspaces/:workspaceId/knowledge-qa/shares/:shareId/evidence/:evidenceId.
// It rechecks source authority for one recipient-bound evidence reference.
func (handler *shareHandler) OpenEvidence(r api.WorkspaceScopedRequest) (api.Response, error) {
	shareID := routeParameter(r.Gin, "shareId")
	evidenceID := routeParameter(r.Gin, "evidenceId")
	if !strings.HasPrefix(shareID, "kqh_") || !validSharedEvidenceID(evidenceID) {
		return api.Response{}, api.ErrNotFound
	}
	if handler.deps.Sharing == nil || handler.deps.Citations == nil {
		return api.Response{}, errors.New("Knowledge Q&A shared citation reader is unavailable")
	}
	shared, err := handler.deps.Sharing.LoadReceivedShareCitation(
		r.Context(), r.UserID(), shareID, evidenceID,
	)
	if err != nil {
		return storeFailure(err)
	}
	citation := shared.Citation
	result, err := handler.deps.Citations.OpenKnowledgeQACitation(
		r.Context(), forwarder.KnowledgeQACitationOpenRequest{
			Principal: forwarder.KnowledgeQARuntimePrincipal{
				OrganizationID: shared.SourceOrganizationID,
				WorkspaceID:    shared.SourceWorkspaceID,
				RequesterID:    r.UserID(), TurnID: shared.SourceTurnID,
			},
			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),
			},
			AllowRestrictedEvidence: true,
		},
	)
	if err != nil {
		return api.Response{}, err
	}
	response := knowledgeQACitationResponse(citation, result, true)
	if response.Status == "available" && response.OpenMode == "source" {
		response.TargetWorkspaceID = shared.SourceWorkspaceID
	}
	return privateShareResponse(response), nil
}

func validSharedEvidenceID(value string) bool {
	if len(value) < 2 || len(value) > 64 || value[0] != 'E' || value[1] < '1' || value[1] > '9' {
		return false
	}
	for index := 2; index < len(value); index++ {
		if value[index] < '0' || value[index] > '9' {
			return false
		}
	}
	return true
}

// List handles GET /api/v1/workspaces/:workspaceId/knowledge-qa/shares.
// It returns the bounded received-share projection for the authenticated Human.
func (handler *shareHandler) List(r api.WorkspaceScopedRequest) (api.Response, error) {
	if handler.deps.Sharing == nil {
		return api.Response{}, errors.New("Knowledge Q&A sharing service is unavailable")
	}
	shares, err := handler.deps.Sharing.ListReceivedShares(
		r.Context(), r.UserID(), 20,
	)
	if err != nil {
		return storeFailure(err)
	}
	items := make([]dto.KnowledgeQAShareSummary, 0, len(shares))
	for _, share := range shares {
		items = append(items, dto.KnowledgeQAShareSummary{
			ID:               share.ID,
			OwnerDisplayName: share.OwnerDisplayName,
			Question:         share.Question,
			CreatedAt:        share.CreatedAt,
			LastOpenedAt:     share.LastOpenedAt,
		})
	}
	return privateShareResponse(dto.KnowledgeQAShareListResponse{Items: items}), nil
}

func toShareDTO(share Share) dto.KnowledgeQAShare {
	return dto.KnowledgeQAShare{
		ID:               share.ID,
		OwnerDisplayName: share.OwnerDisplayName,
		Question:         share.Question,
		Answer:           share.Answer,
		CreatedAt:        share.CreatedAt,
		LastOpenedAt:     share.LastOpenedAt,
	}
}

func privateShareResponse(body any) api.Response {
	response := api.OK(body)
	response.Headers = privateShareHeaders()
	return response
}

func privateShareHeaders() http.Header {
	headers := make(http.Header)
	headers.Set("Cache-Control", "private, no-store")
	return headers
}
