package pager

import (
	"context"
	"encoding/json"
	"fmt"
	"log/slog"
	"os"
	"regexp"
	"strings"

	lark "github.com/larksuite/oapi-sdk-go/v3"
	larkcontact "github.com/larksuite/oapi-sdk-go/v3/service/contact/v3"
	larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"

	"github.com/tidbcloud/aisupport/services/oncall/config"
)

type Caller interface {
	Call(number string) error
}

type PageRequest struct {
	UserEmail string `json:"user_email"`
	Reason    string `json:"reason"`
}

type PageResult struct {
	MessageID   string `json:"message_id,omitempty"`
	PhoneNumber string `json:"phone_number,omitempty"`
	CallMethod  string `json:"call_method,omitempty"`
	CallError   string `json:"call_error,omitempty"`
}

// PhoneLookup returns (pagePhone, phone) from the DB.
// pagePhone is the user override; phone is the Feishu-synced snapshot.
// Either may be empty.
type PhoneLookup func(email string) (pagePhone, phone string)

type Pager struct {
	larkClient   *lark.Client
	aliCaller    Caller
	twilioCaller Caller
	phoneLookup  PhoneLookup
}

func New(larkClient *lark.Client, aliCaller, twilioCaller Caller, phoneLookup PhoneLookup) *Pager {
	return &Pager{
		larkClient:   larkClient,
		aliCaller:    aliCaller,
		twilioCaller: twilioCaller,
		phoneLookup:  phoneLookup,
	}
}

// NewFromEnv creates a Pager wired to the embedded config file (config.yaml)
// plus secrets from environment variables. Returns nil if the required Feishu
// credentials are not set. AliCloud and Twilio callers are enabled when their
// respective secret env var is set.
func NewFromEnv() *Pager {
	appID := strings.TrimSpace(os.Getenv("FEISHU_APP_ID"))
	appSecret := strings.TrimSpace(os.Getenv("FEISHU_APP_SECRET"))
	if appID == "" || appSecret == "" {
		return nil
	}
	larkClient := lark.NewClient(appID, appSecret)
	cfg := config.C.Pager

	var aliCaller Caller
	if aliSecret := os.Getenv("ALI_ACCESS_KEY_SECRET"); aliSecret != "" {
		c, err := NewAliCaller(AliCallerConfig{
			AccessKeyID:     cfg.Ali.AccessKeyID,
			AccessKeySecret: aliSecret,
			TtsCode:         cfg.Ali.TtsCode,
			CalloutNumbers:  cfg.Ali.CalloutNumbers,
		})
		if err != nil {
			slog.Warn("pager: ali caller init failed, China phone calls disabled", "error", err)
		} else {
			aliCaller = c
		}
	}

	var twilioCaller Caller
	if twilioToken := os.Getenv("TWILIO_AUTH_TOKEN"); twilioToken != "" {
		callers := make([]TwilioCallerNumber, len(cfg.Twilio.CalloutNumbers))
		for i, e := range cfg.Twilio.CalloutNumbers {
			callers[i] = TwilioCallerNumber{Prefix: e.Prefix, Number: e.Number}
		}
		c, err := NewTwilioCaller(TwilioCallerConfig{
			AccountSID: cfg.Twilio.AccountSID,
			AuthToken:  twilioToken,
			Callers:    callers,
		})
		if err != nil {
			slog.Warn("pager: twilio caller init failed, international phone calls disabled", "error", err)
		} else {
			twilioCaller = c
		}
	}

	return New(larkClient, aliCaller, twilioCaller, nil)
}

func (p *Pager) SetPhoneLookup(fn PhoneLookup) {
	p.phoneLookup = fn
}

func (p *Pager) Page(ctx context.Context, req PageRequest) (*PageResult, error) {
	if req.UserEmail == "" {
		return nil, fmt.Errorf("user_email is required")
	}

	openID, phone, hasCustomPhone, err := p.resolveUser(ctx, req.UserEmail)
	if err != nil {
		return nil, fmt.Errorf("resolve user %s: %w", req.UserEmail, err)
	}

	msgID, err := p.sendPageMessage(ctx, openID, req.Reason)
	if err != nil {
		return nil, fmt.Errorf("send page message to %s: %w", req.UserEmail, err)
	}

	result := &PageResult{
		MessageID:   msgID,
		PhoneNumber: phone,
	}

	method, callErr := p.callPhone(ctx, phone, hasCustomPhone, msgID, openID)
	result.CallMethod = method
	if callErr != nil {
		slog.Warn("pager: phone call failed", "error", callErr, "user", req.UserEmail, "phone", phone, "method", method)
		result.CallError = callErr.Error()
	}

	return result, nil
}

func (p *Pager) resolveUser(ctx context.Context, email string) (openID, phone string, hasCustomPhone bool, err error) {
	batchReq := larkcontact.NewBatchGetIdUserReqBuilder().
		UserIdType("open_id").
		Body(larkcontact.NewBatchGetIdUserReqBodyBuilder().
			Emails([]string{email}).
			Build()).
		Build()
	batchResp, err := p.larkClient.Contact.User.BatchGetId(ctx, batchReq)
	if err != nil {
		return "", "", false, fmt.Errorf("batch get id: %w", err)
	}
	if !batchResp.Success() {
		return "", "", false, fmt.Errorf("batch get id: code=%d msg=%s", batchResp.Code, batchResp.Msg)
	}

	if batchResp.Data != nil {
		for _, u := range batchResp.Data.UserList {
			if u.UserId != nil && *u.UserId != "" {
				openID = *u.UserId
				break
			}
		}
	}
	if openID == "" {
		return "", "", false, fmt.Errorf("user not found in Feishu")
	}

	if p.phoneLookup != nil {
		pagePhone, dbPhone := p.phoneLookup(email)
		if pagePhone != "" {
			return openID, pagePhone, true, nil
		}
		if dbPhone != "" {
			return openID, dbPhone, false, nil
		}
	}

	getResp, err := p.larkClient.Contact.User.Get(ctx,
		larkcontact.NewGetUserReqBuilder().
			UserId(openID).
			UserIdType("open_id").
			Build())
	if err != nil {
		return openID, "", false, nil
	}
	if getResp.Success() && getResp.Data != nil && getResp.Data.User != nil {
		if getResp.Data.User.Mobile != nil {
			phone = *getResp.Data.User.Mobile
		}
	}
	return openID, phone, false, nil
}

func (p *Pager) sendPageMessage(ctx context.Context, openID, reason string) (string, error) {
	text := "You are being paged"
	if reason != "" {
		text += ": " + reason
	}
	content, _ := json.Marshal(map[string]string{"text": text})

	req := larkim.NewCreateMessageReqBuilder().
		ReceiveIdType("open_id").
		Body(larkim.NewCreateMessageReqBodyBuilder().
			ReceiveId(openID).
			MsgType("text").
			Content(string(content)).
			Build()).
		Build()

	resp, err := p.larkClient.Im.Message.Create(ctx, req)
	if err != nil {
		return "", err
	}
	if !resp.Success() {
		return "", fmt.Errorf("code=%d msg=%s", resp.Code, resp.Msg)
	}
	if resp.Data == nil || resp.Data.MessageId == nil {
		return "", fmt.Errorf("no message_id returned")
	}
	return *resp.Data.MessageId, nil
}

var chinaPhoneRegex = regexp.MustCompile(`^(\+?86)`)

func isDomestic(phone string) bool {
	return chinaPhoneRegex.MatchString(phone)
}

func (p *Pager) callPhone(ctx context.Context, phone string, hasCustomPhone bool, msgID, openID string) (method string, err error) {
	if hasCustomPhone {
		if isDomestic(phone) {
			if p.aliCaller == nil {
				return "ali_disabled", fmt.Errorf("ali caller not configured")
			}
			return "ali", p.aliCaller.Call(phone)
		}
		if p.twilioCaller == nil {
			return "twilio_disabled", fmt.Errorf("twilio caller not configured")
		}
		return "twilio", p.twilioCaller.Call(phone)
	}
	if phone == "" || isDomestic(phone) {
		return "feishu_phone", p.urgentPhone(ctx, msgID, openID)
	}
	if p.twilioCaller == nil {
		return "twilio_disabled", fmt.Errorf("twilio caller not configured")
	}
	return "twilio", p.twilioCaller.Call(phone)
}

func (p *Pager) urgentPhone(ctx context.Context, msgID, openID string) error {
	req := larkim.NewUrgentPhoneMessageReqBuilder().
		MessageId(msgID).
		UserIdType("open_id").
		UrgentReceivers(larkim.NewUrgentReceiversBuilder().
			UserIdList([]string{openID}).
			Build()).
		Build()
	resp, err := p.larkClient.Im.Message.UrgentPhone(ctx, req)
	if err != nil {
		return err
	}
	if !resp.Success() {
		return fmt.Errorf("code=%d msg=%s", resp.Code, resp.Msg)
	}
	return nil
}
