package es

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"

	"github.com/dchest/uniuri"

	"github.com/pingcap-inc/clinic/pkg/cache"
	"github.com/pingcap-inc/clinic/pkg/db/model"

	"github.com/pingcap/errors"
	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/config"
)

const roleTemplate = `{
  "cluster_permissions": ["get"],
  "index_permissions": [
    {
      "index_patterns": [
        %s
      ],
      "dls": "",
      "fls": [],
      "masked_fields": [],
      "allowed_actions": ["get","search", "read"]
    }
  ],
  "tenant_permissions":[{"tenant_patterns":["%s"],"allowed_actions":["kibana_all_read"]}]
}`

const roleMappingTemplate = `{
  "users": [
    "%s"
  ],
  "backend_roles": [],
  "hosts": []
}`

const userTemplate = `{
  "backend_roles": [
    ""
  ],
  "attributes": {},
  "password": "%s"
}`

var AwsKibanaPrefix = "/kibana"

type KibanaSaveObjects struct {
	SaveObjects []KibanaSaveObject `json:"saved_objects"`
}

type KibanaSaveObject struct {
	ID string `json:"id"`
}

func SearchKibanaIndexPattern(orgID uint, cfg config.LogConfig, index string) (string, error) {
	req, err := http.NewRequest("GET", fmt.Sprintf("%s%s/api/saved_objects/_find?fields=title&fields=type&fields=typeMeta&per_page=1&type=index-pattern&search=%s", getKibanaAddr(cfg), AwsKibanaPrefix, index), nil)
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("osd-xsrf", "true")
	req.Header.Set("securitytenant", GetOrgName(orgID))
	req.Header.Set("sgtenant", GetOrgName(orgID))
	//ku, err := model.GetByOrgID(orgID)
	//if err != nil {
	//	log2.Error("query kibanan info from db failed", zap.Uint("org", orgID), zap.String("clusterID", index), zap.Error(err))
	//	return "", err
	//}
	req.SetBasicAuth(cfg.User, cfg.Password)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	var objects KibanaSaveObjects
	if err := json.Unmarshal(data, &objects); err != nil {
		return "", err
	}

	if len(objects.SaveObjects) == 0 {
		return "", nil
	}

	return objects.SaveObjects[0].ID, nil
}

func CreateIndexPatternIfAbsent(orgID uint, cfg config.LogConfig, index string) {
	indexPattern, err := SearchKibanaIndexPattern(orgID, cfg, index)
	if err != nil {
		log.Error("search kibanan index pattern failed", zap.String("clusterID", index), zap.Error(err))
		return
	}

	if indexPattern != "" {
		return
	}

	if err := initKibanaIndexPattern(orgID, getKibanaAddr(cfg), index, cfg); err != nil {
		log.Error("create kibanan index pattern failed", zap.String("clusterID", index), zap.Error(err))
	}
}

func initKibanaIndexPattern(orgID uint, kibanaAddr, index string, cfg config.LogConfig) error {
	body := `{              
    "attributes": {
        "fieldAttrs": "{}",
        "title": "%s",
        "timeFieldName": "timestamp",
        "fields": "[]",
        "typeMeta": "{}",
        "runtimeFieldMap": "{}"
    }
}`
	req, err := http.NewRequest("POST", fmt.Sprintf("%s%s/api/saved_objects/index-pattern", kibanaAddr, AwsKibanaPrefix), strings.NewReader(fmt.Sprintf(body, index)))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("osd-xsrf", "true")
	req.Header.Set("securitytenant", GetOrgName(orgID))
	req.Header.Set("sgtenant", GetOrgName(orgID))
	req.SetBasicAuth(cfg.User, cfg.Password)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	log.Info("req cookies", zap.Any("cookie", req.Cookies()))
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		data, _ := io.ReadAll(resp.Body)
		return errors.New(fmt.Sprintf("create index pattern failed. code=%d, status=%s", resp.StatusCode, data))
	}

	return nil
}

func getKibanaAddr(cfg config.LogConfig) string {
	return fmt.Sprintf("http://%s:%d", cfg.KibanaConfig.Host, cfg.KibanaConfig.Port)
}

type user struct {
	name string
	pass string
}

func createUser(orgID uint, cfg config.LogConfig) (*user, error) {
	u := &user{
		name: fmt.Sprintf("org%d-%s", orgID, uniuri.NewLen(5)),
		pass: fmt.Sprintf("%sPa#%d", uniuri.NewLen(8), orgID),
	}

	path := fmt.Sprintf("/api/v1/configuration/internalusers/%s", u.name)
	_, err := kibanaAdminHttp(orgID, "POST", path, fmt.Sprintf(userTemplate, u.pass), cfg)
	if err != nil {
		return nil, err
	}
	return u, nil
}

func attachRoleToUser(orgID uint, role, user string, cfg config.LogConfig) error {
	path := fmt.Sprintf("/api/v1/configuration/rolesmapping/%s", role)
	_, err := kibanaAdminHttp(orgID, "POST", path, fmt.Sprintf(roleMappingTemplate, user), cfg)
	if err != nil {
		return err
	}
	return nil
}

func DeleteOrg(orgID uint, cfg config.LogConfig) {
	ku, err := model.GetByOrgID(orgID)
	if err != nil {
		log.Error("query org from db failed", zap.Uint("org", orgID), zap.Error(err))
		return
	}
	_, err = kibanaAdminHttp(orgID, "DELETE", fmt.Sprintf("/api/v1/configuration/internalusers/%s", ku.User), "", cfg)
	if err != nil {
		log.Error("delete role from kibana failed", zap.Uint("org", orgID), zap.Error(err))
	}

	_, err = kibanaAdminHttp(orgID, "DELETE", fmt.Sprintf("/api/v1/configuration/tenants/%s", GetOrgName(orgID)), "", cfg)
	if err != nil {
		log.Error("delete org from kibana failed", zap.Uint("org", orgID), zap.Error(err))
	}

	_, err = kibanaAdminHttp(orgID, "DELETE", fmt.Sprintf("/api/v1/configuration/roles/%s", GetRoleName(orgID)), "", cfg)
	if err != nil {
		log.Error("delete role from kibana failed", zap.Uint("org", orgID), zap.Error(err))
	}

	err = model.DeleteKibana(orgID)
	if err != nil {
		log.Error("delete role from db failed", zap.Uint("org", orgID), zap.Error(err))
	}

	cache.KibanaCache.Delete(orgID)
}

func CreateOrg(orgID uint, cfg config.LogConfig) error {
	if err := createOrg(orgID, cfg); err != nil {
		log.Error("create tenant failed, ", zap.Uint("org", orgID), zap.Error(err))
		return err
	}
	if err := createRole(fmt.Sprintf(roleTemplate, fmt.Sprintf("\"%s\",\"%s\"", GetLogIndexName(orgID), GetSlowLogIndexName(orgID)), GetOrgName(orgID)), orgID, cfg); err != nil {
		log.Error("create role failed, ", zap.Uint("org", orgID), zap.Error(err))
		return err
	}

	user, err := createUser(orgID, cfg)
	if err != nil {
		log.Error("create user failed", zap.Uint("org", orgID), zap.Error(err))
		return err
	}

	if err := attachRoleToUser(orgID, GetRoleName(orgID), user.name, cfg); err != nil {
		log.Error("attach role to user failed", zap.Uint("org", orgID), zap.String("user", user.name), zap.Error(err))
		return err
	}

	cache.KibanaCache.Set(orgID, &cache.KibanaUser{
		Name:     user.name,
		Password: user.pass,
	})
	return model.AddOrg(orgID, user.name, user.pass)
}

func GetRoleName(orgID uint) string {
	return fmt.Sprintf("role%d", orgID)
}

func GetOrgName(orgID uint) string {
	return fmt.Sprintf("tenant%d", orgID)
}

func GetOrgFromName(orgName string) uint {
	oidStr := strings.TrimPrefix(orgName, "tenant")
	orgID, err := strconv.ParseUint(oidStr, 10, 64)
	if err != nil {
		return 0
	}

	return uint(orgID)
}

func GetLogIndexName(orgID uint) string {
	return fmt.Sprintf("org%d_log", orgID)
}

func GetSlowLogIndexName(orgID uint) string {
	return fmt.Sprintf("org%d_slowlog", orgID)
}

func createRole(roleAttribute string, orgID uint, cfg config.LogConfig) error {
	path := fmt.Sprintf("/api/v1/configuration/roles/%s", GetRoleName(orgID))
	_, err := kibanaAdminHttp(orgID, "POST", path, roleAttribute, cfg)

	return err
}

func createOrg(orgID uint, cfg config.LogConfig) error {
	body := `{
		"description": "%s"
	}`
	name := GetOrgName(orgID)
	path := fmt.Sprintf("/api/v1/configuration/tenants/%s", name)
	_, err := kibanaAdminHttp(orgID, "POST", path, fmt.Sprintf(body, name), cfg)
	if err != nil {
		return err
	}
	return nil
}

func kibanaAdminHttp(orgID uint, method, path, body string, cfg config.LogConfig) ([]byte, error) {
	req, err := http.NewRequest(method, fmt.Sprintf("%s%s%s", getKibanaAddr(cfg), AwsKibanaPrefix, path), strings.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("osd-xsrf", "true")
	req.Header.Set("securitytenant", GetOrgName(orgID))
	req.Header.Set("sgtenant", GetOrgName(orgID))
	req.SetBasicAuth(cfg.User, cfg.Password)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	data, err := io.ReadAll(resp.Body)
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("kibana response failed, code=%d, status=%s, data=%s", resp.StatusCode, resp.Status, string(data))
	}

	return data, err
}

func SwitchOrg(orgID uint, userName, pwd string, cfg config.LogConfig) error {
	req, err := http.NewRequest("POST", fmt.Sprintf("%s%s%s", getKibanaAddr(cfg), AwsKibanaPrefix, "api/v1/multitenancy/tenant"), strings.NewReader(fmt.Sprintf(SwitchTemplate, GetOrgName(orgID), userName)))
	if err != nil {
		return err
	}
	req.Header.Set("securitytenant", GetOrgName(orgID))
	req.Header.Set("sgtenant", GetOrgName(orgID))
	req.SetBasicAuth(userName, pwd)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("kibana response failed, code=%d, status=%s, data=%s", resp.StatusCode, resp.Status, string(data))
	}

	return nil
}

const SwitchTemplate = `{"tenant":"%s","username":"%s"}`
