package http

import (
	"context"
	"fmt"
	"net/http"
	"regexp"
	"strconv"
	"strings"

	"google.golang.org/grpc/metadata"
)

const (
	setHeaderPrefix    = "x-set-header_"
	queryHeaderPrefix  = "x-query-header_"
	cookieHeaderPrefix = "x-cookie-header_"
	httpClientIP       = "x-forwarded-for"
	httpURI            = "x-http-uri"
	httpMethod         = "x-http-method"
	httpPath           = "x-forwarded-path"
)

// FirstHeader retrieves the header content from the context.
// If multiples of the header were given, it gives the first.
func FirstHeader(ctx context.Context, header string) string {
	headers, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return ""
	}
	headerTexts := headers[strings.ToLower(header)]
	if len(headerTexts) < 1 {
		return ""
	}
	return headerTexts[0]
}

func ClientIP(ctx context.Context) string {
	header := FirstHeader(ctx, httpClientIP)
	if header != "" {
		ips := strings.Split(header, ",")
		if len(ips) > 0 {
			return ips[0]
		}
	}
	return ""
}

func HttpURI(ctx context.Context) string {
	return FirstHeader(ctx, httpURI)
}

func HttpMethod(ctx context.Context) string {
	return FirstHeader(ctx, httpMethod)
}

func HttpPath(ctx context.Context) string {
	return FirstHeader(ctx, httpPath)
}

func Query(ctx context.Context, name string) string {
	return FirstHeader(ctx, strings.ToLower(queryHeaderPrefix+name))
}

func Port2Address(port int) string {
	return fmt.Sprintf("0.0.0.0:%d", port)
}

func AllHeaderMatcher(key string) (string, bool) {
	if strings.EqualFold(key, "connection") {
		return "", false
	}
	return key, true
}

func AddHeaderInResponseMatcher(key string) (string, bool) {
	if strings.HasPrefix(key, setHeaderPrefix) {
		return strings.TrimPrefix(key, setHeaderPrefix), true
	}
	return key, false
}

func AddHttpDetail(_ context.Context, r *http.Request) metadata.MD {
	m := make(map[string]string)
	m[httpURI] = r.RequestURI
	m[httpPath] = r.URL.Path
	m[httpMethod] = r.Method
	for key, value := range r.URL.Query() {
		m[queryHeaderPrefix+key] = value[0]
	}
	for _, c := range r.Cookies() {
		m[cookieHeaderPrefix+c.Name] = c.Value
	}
	return metadata.New(m)
}

var (
	OrgIdURLPattern               = regexp.MustCompile(`/orgs/(?P<org_id>\d+)`)
	ProjectIdURLPattern           = regexp.MustCompile(`/projects/(?P<project_id>\d+)`)
	ClusterIdURLPattern           = regexp.MustCompile(`/clusters/(?P<cluster_id>\d+)`)
	RecycledInstanceIDURLPattern  = regexp.MustCompile(`/recycledInstances/(?P<cluster_id>\d+)`)
	BranchIdURLPattern            = regexp.MustCompile(`/branches/(?P<branch_id>[\w-]+)`)
	ExportIdURLPattern            = regexp.MustCompile(`/exports/(?P<export_id>[\w-]+)`)
	ImportIdURLPattern            = regexp.MustCompile(`/imports/(?P<import_id>[\w-]+)`)
	MigrationTaskIdURLPattern     = regexp.MustCompile(`/migrations/(?P<migration_id>[\w-]+)`)
	MigrationPrecheckIdURLPattern = regexp.MustCompile(`/migrationPrechecks/(?P<precheck_id>[\w-]+)`)
)

func GetOrgIdFromPath(path string) (uint64, bool) {
	if index := OrgIdURLPattern.SubexpIndex("org_id"); index != -1 {
		res := OrgIdURLPattern.FindStringSubmatch(path)
		if res != nil {
			orgId, _ := strconv.ParseUint(res[index], 10, 64)
			return orgId, true
		}
	}
	return 0, false
}

func GetProjectIdFromPath(path string) (uint64, bool) {
	if index := ProjectIdURLPattern.SubexpIndex("project_id"); index != -1 {
		res := ProjectIdURLPattern.FindStringSubmatch(path)
		if res != nil {
			projectId, _ := strconv.ParseUint(res[index], 10, 64)
			return projectId, true
		}
	}
	return 0, false
}

func GetClusterIdFromPath(path string) (uint64, bool) {
	if index := ClusterIdURLPattern.SubexpIndex("cluster_id"); index != -1 {
		res := ClusterIdURLPattern.FindStringSubmatch(path)
		if res != nil {
			clusterId, _ := strconv.ParseUint(res[index], 10, 64)
			return clusterId, true
		}
	}
	if index := RecycledInstanceIDURLPattern.SubexpIndex("cluster_id"); index != -1 {
		res := RecycledInstanceIDURLPattern.FindStringSubmatch(path)
		if res != nil {
			clusterId, _ := strconv.ParseUint(res[index], 10, 64)
			return clusterId, true
		}
	}
	return 0, false
}

func GetBranchIdFromPath(path string) (string, bool) {
	if index := BranchIdURLPattern.SubexpIndex("branch_id"); index != -1 {
		res := BranchIdURLPattern.FindStringSubmatch(path)
		if res != nil {
			return res[index], true
		}
	}
	return "", false
}

func GetExportIdFromPath(path string) (string, bool) {
	if index := ExportIdURLPattern.SubexpIndex("export_id"); index != -1 {
		res := ExportIdURLPattern.FindStringSubmatch(path)
		if res != nil {
			return res[index], true
		}
	}
	return "", false
}

func GetImportIdFromPath(path string) (string, bool) {
	if index := ImportIdURLPattern.SubexpIndex("import_id"); index != -1 {
		res := ImportIdURLPattern.FindStringSubmatch(path)
		if res != nil {
			return res[index], true
		}
	}
	return "", false
}

func GetMigrationIdFromPath(path string) (string, bool) {
	if index := MigrationTaskIdURLPattern.SubexpIndex("migration_id"); index != -1 {
		res := MigrationTaskIdURLPattern.FindStringSubmatch(path)
		if res != nil {
			return res[index], true
		}
	}
	return "", false
}

func GetMigrationPrecheckIdFromPath(path string) (string, bool) {
	if index := MigrationPrecheckIdURLPattern.SubexpIndex("precheck_id"); index != -1 {
		res := MigrationPrecheckIdURLPattern.FindStringSubmatch(path)
		if res != nil {
			return res[index], true
		}
	}
	return "", false
}

var ProjectIdParamPattern = regexp.MustCompile(`project_id=(?P<project_id>\d+)`)

func GetProjectIdFromURI(url string) (uint64, bool) {
	if index := ProjectIdParamPattern.SubexpIndex("project_id"); index != -1 {
		res := ProjectIdParamPattern.FindStringSubmatch(url)
		if res != nil {
			projectId, _ := strconv.ParseUint(res[index], 10, 64)
			return projectId, true
		}
	}
	return 0, false
}
