package receiver

import (
	"bytes"
	"errors"
	"fmt"
	"strings"
	"text/template"

	"github.com/spf13/viper"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	glogger "gorm.io/gorm/logger"

	eutil "jiraport/pkg/errors"
	"jiraport/pkg/log"
	"jiraport/pkg/matcher"
	eng "jiraport/resolver/engineer"
	"jiraport/service/jira"
)

type GroupReceiver string

// impl Receiver
func (sr *GroupReceiver) Type() string {
	return GroupReceiverType
}

// impl Receiver
func (sr *GroupReceiver) Id() string {
	return string(*sr)
}

type RegionCCReceiverSelector struct {
	infoDB           *gorm.DB
	policyDB         *gorm.DB
	regionReceivers  map[string]GroupReceiver
	tenantExtractor  *template.Template
	clusterExtractor *template.Template
}

func NewRegionCCReceiverSelector(policyDB *gorm.DB) (*RegionCCReceiverSelector, error) {
	infoDBDSN := viper.GetString("info-db-dsn")
	gormLoggerLevel := glogger.Warn
	if viper.GetString("mode") == "debug" {
		gormLoggerLevel = glogger.Info
	}
	var infoDB *gorm.DB
	if infoDBDSN != "" {
		db, err := gorm.Open(mysql.Open(infoDBDSN), &gorm.Config{
			PrepareStmt: true,
			Logger:      glogger.Default.LogMode(gormLoggerLevel),
		})
		if err != nil {
			return nil, err
		}
		log.Debug("msg", "connected to info db")
		infoDB = db
	}

	var regionReceivers map[string]GroupReceiver
	// NOTE: all key will be lowercase, see https://github.com/spf13/viper/issues/1014
	err := viper.UnmarshalKey("region-receivers", &regionReceivers)
	if err != nil {
		return nil, err
	}
	log.Debug("msg", "load region-receivers", "region-receivers", &regionReceivers)

	parseExtractor := func(e string) (*template.Template, error) {
		tmpl := template.New("").Option("missingkey=zero").Funcs(matcher.CommonFuncs)
		tmpl, err = tmpl.Parse(e)
		if err != nil {
			return nil, fmt.Errorf("parse error in %#v, %w", e, err)
		}
		return tmpl, nil
	}
	tenantExtractor, err := parseExtractor(viper.GetString("tenant-extractor"))
	if err != nil {
		return nil, err
	}
	clusterExtractor, err := parseExtractor(viper.GetString("cluster-extractor"))
	if err != nil {
		return nil, err
	}

	return &RegionCCReceiverSelector{
		infoDB:           infoDB,
		policyDB:         policyDB,
		regionReceivers:  regionReceivers,
		tenantExtractor:  tenantExtractor,
		clusterExtractor: clusterExtractor,
	}, nil
}

func (rs *RegionCCReceiverSelector) SelectReceiver(name string, data interface{}) ([]Receiver, error) {
	if name != "regioncc" {
		return nil, nil
	}
	if rs.infoDB == nil {
		return nil, errors.New("no valid info-db, regioncc is not allowd")
	}
	issue, ok := data.(*jira.Issue)
	if !ok {
		return nil, nil
	}

	rawIssue := &issue.RawIssue

	var buf bytes.Buffer
	err := rs.tenantExtractor.Execute(&buf, rawIssue)
	if err != nil {
		return nil, err
	}
	tenant := buf.String()

	buf.Reset()
	err = rs.clusterExtractor.Execute(&buf, rawIssue)
	if err != nil {
		return nil, err
	}
	cluster := buf.String()

	log.Debug("msg", "extracted tenant and cluster", "tenant", tenant, "cluster", cluster)

	receivers := make([]Receiver, 0)

	// select group
	region, err := GetRegionByTenantIDAndClusterID(rs.infoDB, tenant, cluster)
	if err != nil {
		log.Error("msg", "get region by by tenent and cluster error", "err", err)
	} else {
		// NOTE: msut use lowercase region, see https://github.com/spf13/viper/issues/1014
		region = strings.ToLower(region)
		regionReceiver, ok := rs.regionReceivers[region]
		if !ok {
			log.Error("msg", "not found region in region receivers", "region", region)
		} else {
			receivers = append(receivers, &regionReceiver)
		}
	}

	// select person contacts
	engineers, err := GetContactsByTenantIDAndClusterID(rs.policyDB, rs.infoDB, tenant, cluster)
	if err != nil {
		log.Error("msg", "get contacts by tenent and cluster error", "err", err)
	} else {
		for i := range engineers {
			receivers = append(receivers, &engineers[i])
		}
	}

	log.Debug("msg", "get regioncc", "receivers", receivers)

	return receivers, nil
}

func GetContactsByTenantIDAndClusterID(policyDB, infoDB *gorm.DB, tenantID string, clusterID string) ([]eng.Engineer, error) {
	const SELECT_ENGINEERS_BY_TENANT_ID_AND_CLUSTER_ID = `
        SELECT DISTINCT
            t.region_contact region_contact, t.rd_contact rd_contact
        FROM
            jirainterval.tmp_tenants t
                LEFT JOIN
            tmp_dw.tmp_curr_clusters c ON t.id = c.tenant_id
        WHERE
            c.id = ? OR t.id = ?
        LIMIT 1
    `
	type Contacts struct {
		RegionContact string `gorm:"column:region_contact"`
		RDContact     string `gorm:"column:rd_contact"`
	}
	contacts := Contacts{}
	res := infoDB.Raw(SELECT_ENGINEERS_BY_TENANT_ID_AND_CLUSTER_ID, clusterID, tenantID).Scan(&contacts)
	err := eutil.CheckGORMErr(res)
	if err != nil {
		return nil, err
	}

	emails := []string{contacts.RegionContact, contacts.RDContact}

	log.Debug("msg", "get contact email", "email", emails)

	engineers := make([]eng.Engineer, 0, len(emails))
	for _, email := range emails {
		// allow empty contact
		if email == "" {
			continue
		}
		engineer, err := eng.GetEngineerByEmail(policyDB, email)
		if err != nil {
			return nil, err
		}
		engineers = append(engineers, *engineer)
	}

	log.Debug("msg", "get contact", "contact", engineers)
	return engineers, nil
}

func GetRegionByTenantIDAndClusterID(infoDB *gorm.DB, tenantID string, clusterID string) (string, error) {
	const SELECT_REGION_BY_TENANT_ID_AND_CLUSTER_ID = `
        SELECT DISTINCT
            IFNULL(r.region, c.region) region
        FROM
            jirainterval.tmp_tenants t
                LEFT JOIN
            tmp_dw.tmp_curr_clusters c ON t.id = c.tenant_id
                LEFT JOIN
            tmp_dw.tmp_real_region r ON t.id = r.tenant_id
        WHERE
            c.id = ? OR t.id = ?
        LIMIT 1
    `
	var region string
	res := infoDB.Raw(SELECT_REGION_BY_TENANT_ID_AND_CLUSTER_ID, clusterID, tenantID).Scan(&region)
	return region, eutil.CheckGORMErr(res)
}
