package router

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/url"
	"strings"

	repositoryroute "github.com/pingcap/agent-git-service/repositoryroutecontract"
)

type workspaceRawResponse struct {
	header http.Header
	body   bytes.Buffer
	status int
}

func (response *workspaceRawResponse) Header() http.Header {
	if response.header == nil {
		response.header = make(http.Header)
	}
	return response.header
}

func (response *workspaceRawResponse) WriteHeader(status int) {
	if response.status == 0 {
		response.status = status
	}
}

func (response *workspaceRawResponse) Write(body []byte) (int, error) {
	if response.status == 0 {
		response.status = http.StatusOK
	}
	return response.body.Write(body)
}

func serveWorkspaceRawContent(next http.Handler, writer http.ResponseWriter, request *http.Request, raw repositoryroute.RawRequest) {
	contents := request.Clone(request.Context())
	contents.URL.Path = "/api/v3/repos/" + raw.Owner + "/" + raw.Repository + "/contents/" + raw.FilePath
	contents.URL.RawPath = "/api/v3/repos/" + url.PathEscape(raw.Owner) + "/" + url.PathEscape(raw.Repository) + "/contents/" + escapeWorkspaceRawPath(raw.FilePath)
	query := contents.URL.Query()
	query.Set("ref", raw.Ref)
	contents.URL.RawQuery = query.Encode()
	if contents.RequestURI != "" {
		contents.RequestURI = contents.URL.RequestURI()
	}

	response := &workspaceRawResponse{}
	next.ServeHTTP(response, contents)
	status := response.status
	if status == 0 {
		status = http.StatusOK
	}
	if status < http.StatusOK || status >= http.StatusMultipleChoices {
		copyWorkspaceRawHeaders(writer.Header(), response.Header())
		writer.WriteHeader(status)
		_, _ = writer.Write(response.body.Bytes())
		return
	}

	var payload struct {
		Name     string `json:"name"`
		Type     string `json:"type"`
		Encoding string `json:"encoding"`
		Content  string `json:"content"`
	}
	if err := json.Unmarshal(response.body.Bytes(), &payload); err != nil || payload.Type != "file" || payload.Encoding != "base64" {
		http.Error(writer, "invalid AGS raw content response", http.StatusBadGateway)
		return
	}
	content, err := base64.StdEncoding.DecodeString(stripWorkspaceRawBase64Whitespace(payload.Content))
	if err != nil {
		http.Error(writer, "invalid AGS raw content response", http.StatusBadGateway)
		return
	}
	writer.Header().Set("Content-Type", http.DetectContentType(content))
	if payload.Name != "" {
		writer.Header().Set("Content-Disposition", `inline; filename="`+strings.ReplaceAll(payload.Name, `"`, "")+`"`)
	}
	writer.WriteHeader(http.StatusOK)
	_, _ = writer.Write(content)
}

func copyWorkspaceRawHeaders(destination, source http.Header) {
	for name, values := range source {
		for _, value := range values {
			destination.Add(name, value)
		}
	}
}

func escapeWorkspaceRawPath(path string) string {
	parts := strings.Split(path, "/")
	for index, part := range parts {
		parts[index] = url.PathEscape(part)
	}
	return strings.Join(parts, "/")
}

func stripWorkspaceRawBase64Whitespace(value string) string {
	return strings.Map(func(char rune) rune {
		switch char {
		case ' ', '\n', '\r', '\t':
			return -1
		default:
			return char
		}
	}, value)
}
