package service

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"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"
)

func CheckFeishuUserExists(email string) (bool, error) {
	email = normalizeEmail(email)
	if email == "" {
		return false, fmt.Errorf("empty email")
	}

	appID := strings.TrimSpace(os.Getenv("FEISHU_APP_ID"))
	appSecret := strings.TrimSpace(os.Getenv("FEISHU_APP_SECRET"))
	if appID == "" || appSecret == "" {
		if ShouldBypassAuth() {
			return IsEmailAllowed(email), nil
		}
		return false, fmt.Errorf("FEISHU_APP_ID or FEISHU_APP_SECRET not set")
	}

	client := lark.NewClient(appID, appSecret)

	req := larkcontact.NewBatchGetIdUserReqBuilder().
		// 使用 open_id 避免 contact:user.employee_id:readonly 额外权限要求。
		UserIdType("open_id").
		Body(larkcontact.NewBatchGetIdUserReqBodyBuilder().
			Emails([]string{email}).
			Build()).
		Build()

	resp, err := client.Contact.User.BatchGetId(context.Background(), req)
	if err != nil {
		return false, err
	}
	if !resp.Success() {
		return false, fmt.Errorf("msg:%s,code:%d", resp.Msg, resp.Code)
	}

	if resp.Data != nil && resp.Data.UserList != nil {
		for _, user := range resp.Data.UserList {
			if user.UserId != nil && *user.UserId != "" {
				return true, nil
			}
		}
	}
	return false, nil
}

func ResolveFeishuUserIDByEmail(email string) (string, error) {
	email = normalizeEmail(email)
	if email == "" {
		return "", fmt.Errorf("empty email")
	}

	appID := strings.TrimSpace(os.Getenv("FEISHU_APP_ID"))
	appSecret := strings.TrimSpace(os.Getenv("FEISHU_APP_SECRET"))
	if appID == "" || appSecret == "" {
		return "", fmt.Errorf("FEISHU_APP_ID or FEISHU_APP_SECRET not set")
	}

	client := lark.NewClient(appID, appSecret)
	req := larkcontact.NewBatchGetIdUserReqBuilder().
		UserIdType("user_id").
		Body(larkcontact.NewBatchGetIdUserReqBodyBuilder().
			Emails([]string{email}).
			Build()).
		Build()

	resp, err := client.Contact.User.BatchGetId(context.Background(), req)
	if err != nil {
		return "", err
	}
	if !resp.Success() {
		return "", fmt.Errorf("msg:%s,code:%d", resp.Msg, resp.Code)
	}
	if resp.Data != nil && resp.Data.UserList != nil {
		for _, user := range resp.Data.UserList {
			if user.UserId != nil && *user.UserId != "" {
				return *user.UserId, nil
			}
		}
	}
	return "", fmt.Errorf("feishu user not found for email %s", email)
}

func ResolveFeishuOpenIDsByEmail(emails []string) (map[string]string, error) {
	normalized := uniqEmails(emails)
	if len(normalized) == 0 {
		return map[string]string{}, nil
	}

	appID := strings.TrimSpace(os.Getenv("FEISHU_APP_ID"))
	appSecret := strings.TrimSpace(os.Getenv("FEISHU_APP_SECRET"))
	if appID == "" || appSecret == "" {
		return nil, fmt.Errorf("FEISHU_APP_ID or FEISHU_APP_SECRET not set")
	}

	client := lark.NewClient(appID, appSecret)
	resolved := make(map[string]string, len(normalized))
	for i := 0; i < len(normalized); i += 50 {
		end := i + 50
		if end > len(normalized) {
			end = len(normalized)
		}

		req := larkcontact.NewBatchGetIdUserReqBuilder().
			UserIdType("open_id").
			Body(larkcontact.NewBatchGetIdUserReqBodyBuilder().
				Emails(normalized[i:end]).
				Build()).
			Build()

		resp, err := client.Contact.User.BatchGetId(context.Background(), req)
		if err != nil {
			return nil, err
		}
		if !resp.Success() {
			return nil, fmt.Errorf("msg:%s,code:%d", resp.Msg, resp.Code)
		}
		if resp.Data == nil || resp.Data.UserList == nil {
			continue
		}
		for _, user := range resp.Data.UserList {
			if user == nil || user.Email == nil || user.UserId == nil || *user.UserId == "" {
				continue
			}
			resolved[normalizeEmail(*user.Email)] = *user.UserId
		}
	}
	return resolved, nil
}

type FeishuUserProfile struct {
	Name   string
	EnName string
	Phone  string
}

// FetchFeishuUserProfile fetches name, en_name, and phone for an email via the
// Feishu contact API. Returns an error if the user is not found or credentials
// are missing; callers should treat errors as a soft failure and fall back.
func FetchFeishuUserProfile(email string) (*FeishuUserProfile, error) {
	email = normalizeEmail(email)
	if email == "" {
		return nil, fmt.Errorf("empty email")
	}

	appID := strings.TrimSpace(os.Getenv("FEISHU_APP_ID"))
	appSecret := strings.TrimSpace(os.Getenv("FEISHU_APP_SECRET"))
	if appID == "" || appSecret == "" {
		return nil, fmt.Errorf("FEISHU credentials not configured")
	}

	client := lark.NewClient(appID, appSecret)

	batchReq := larkcontact.NewBatchGetIdUserReqBuilder().
		UserIdType("open_id").
		Body(larkcontact.NewBatchGetIdUserReqBodyBuilder().
			Emails([]string{email}).
			Build()).
		Build()
	batchResp, err := client.Contact.User.BatchGetId(context.Background(), batchReq)
	if err != nil {
		return nil, err
	}
	if !batchResp.Success() {
		return nil, fmt.Errorf("BatchGetId: code=%d msg=%s", batchResp.Code, batchResp.Msg)
	}

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

	getResp, err := client.Contact.User.Get(context.Background(),
		larkcontact.NewGetUserReqBuilder().
			UserId(openID).
			UserIdType("open_id").
			Build())
	if err != nil {
		return nil, err
	}
	if !getResp.Success() {
		return nil, fmt.Errorf("User.Get: code=%d msg=%s", getResp.Code, getResp.Msg)
	}

	p := &FeishuUserProfile{}
	if getResp.Data != nil && getResp.Data.User != nil {
		u := getResp.Data.User
		if u.Name != nil {
			p.Name = *u.Name
		}
		if u.EnName != nil {
			p.EnName = *u.EnName
		}
		if u.Mobile != nil {
			p.Phone = *u.Mobile
		}
	}
	return p, nil
}

// defaultChatMembers parses FEISHU_CHAT_DEFAULT_MEMBERS (comma-separated
// open_ids / bot app_ids) into user and bot lists. Bot IDs start with "cli_".
func defaultChatMembers() (userIDs, botIDs []string) {
	raw := strings.TrimSpace(os.Getenv("FEISHU_CHAT_DEFAULT_MEMBERS"))
	if raw == "" {
		return nil, nil
	}
	for _, id := range strings.Split(raw, ",") {
		id = strings.TrimSpace(id)
		if id == "" {
			continue
		}
		if strings.HasPrefix(id, "cli_") {
			botIDs = append(botIDs, id)
		} else {
			userIDs = append(userIDs, id)
		}
	}
	return
}

// CreateFeishuGroupChat creates a new Feishu group chat with the given name and
// member emails, returning the chat_id. Members from FEISHU_CHAT_DEFAULT_MEMBERS
// are automatically included.
func CreateFeishuGroupChat(name string, memberEmails []string) (string, error) {
	appID := strings.TrimSpace(os.Getenv("FEISHU_APP_ID"))
	appSecret := strings.TrimSpace(os.Getenv("FEISHU_APP_SECRET"))
	if appID == "" || appSecret == "" {
		return "", fmt.Errorf("FEISHU_APP_ID or FEISHU_APP_SECRET not set")
	}

	openIDs, err := ResolveFeishuOpenIDsByEmail(memberEmails)
	if err != nil {
		return "", fmt.Errorf("failed to resolve member open_ids: %w", err)
	}
	memberIDs := make([]string, 0, len(openIDs))
	for _, id := range openIDs {
		memberIDs = append(memberIDs, id)
	}

	defaultUsers, defaultBots := defaultChatMembers()
	memberIDs = append(memberIDs, defaultUsers...)

	bodyBuilder := larkim.NewCreateChatReqBodyBuilder().
		Name(name).
		ChatMode("group").
		ChatType("private").
		UserIdList(memberIDs)
	if len(defaultBots) > 0 {
		bodyBuilder = bodyBuilder.BotIdList(defaultBots)
	}

	client := lark.NewClient(appID, appSecret)
	req := larkim.NewCreateChatReqBuilder().
		Body(bodyBuilder.Build()).
		UserIdType("open_id").
		Build()

	resp, err := client.Im.Chat.Create(context.Background(), req)
	if err != nil {
		return "", err
	}
	if !resp.Success() {
		return "", fmt.Errorf("failed to create feishu chat: code=%d, msg=%s", resp.Code, resp.Msg)
	}
	if resp.Data == nil || resp.Data.ChatId == nil || *resp.Data.ChatId == "" {
		return "", fmt.Errorf("feishu chat created but no chat_id returned")
	}
	return *resp.Data.ChatId, nil
}

// InviteToFeishuChat invites a list of users (by email) to an existing chat.
func InviteToFeishuChat(chatID string, emails []string) error {
	if chatID == "" || len(emails) == 0 {
		return nil
	}
	appID := strings.TrimSpace(os.Getenv("FEISHU_APP_ID"))
	appSecret := strings.TrimSpace(os.Getenv("FEISHU_APP_SECRET"))
	if appID == "" || appSecret == "" {
		return fmt.Errorf("FEISHU_APP_ID or FEISHU_APP_SECRET not set")
	}

	openIDs, err := ResolveFeishuOpenIDsByEmail(emails)
	if err != nil {
		return fmt.Errorf("failed to resolve open_ids: %w", err)
	}
	ids := make([]string, 0, len(openIDs))
	for _, id := range openIDs {
		ids = append(ids, id)
	}

	defaultUsers, defaultBots := defaultChatMembers()
	ids = append(ids, defaultUsers...)
	ids = append(ids, defaultBots...)
	if len(ids) == 0 {
		return nil
	}

	client := lark.NewClient(appID, appSecret)
	req := larkim.NewCreateChatMembersReqBuilder().
		ChatId(chatID).
		MemberIdType("open_id").
		Body(larkim.NewCreateChatMembersReqBodyBuilder().
			IdList(ids).
			Build()).
		Build()

	resp, err := client.Im.ChatMembers.Create(context.Background(), req)
	if err != nil {
		return err
	}
	if !resp.Success() {
		return fmt.Errorf("failed to invite to chat: code=%d, msg=%s", resp.Code, resp.Msg)
	}
	return nil
}

func SendFeishuMessage(userID, msg string) error {
	return sendFeishuText("user_id", userID, msg)
}

// SendFeishuGroupMessage sends a plain-text message to a group chat (chat_id).
func SendFeishuGroupMessage(chatID, msg string) error {
	return sendFeishuText("chat_id", chatID, msg)
}

// SendFeishuGroupCard sends an interactive card to a group chat (chat_id) and
// returns the created message's ID, so a later tick can patch it in place.
func SendFeishuGroupCard(chatID string, card map[string]interface{}) (string, error) {
	appID := os.Getenv("FEISHU_APP_ID")
	appSecret := os.Getenv("FEISHU_APP_SECRET")
	if appID == "" || appSecret == "" {
		return "", fmt.Errorf("FEISHU_APP_ID or FEISHU_APP_SECRET not set")
	}

	contentBytes, err := json.Marshal(card)
	if err != nil {
		return "", fmt.Errorf("failed to marshal card: %w", err)
	}

	client := lark.NewClient(appID, appSecret)
	req := larkim.NewCreateMessageReqBuilder().
		ReceiveIdType("chat_id").
		Body(larkim.NewCreateMessageReqBodyBuilder().
			ReceiveId(chatID).
			MsgType("interactive").
			Content(string(contentBytes)).
			Build()).
		Build()

	resp, err := client.Im.Message.Create(context.Background(), req)
	if err != nil {
		return "", err
	}
	if !resp.Success() {
		return "", fmt.Errorf("failed to send feishu card: code=%d, msg=%s", resp.Code, resp.Msg)
	}
	if resp.Data == nil || resp.Data.MessageId == nil || *resp.Data.MessageId == "" {
		return "", fmt.Errorf("feishu card sent but no message_id returned")
	}

	return *resp.Data.MessageId, nil
}

// PatchFeishuCardMessage replaces the content of an already-sent card message
// in place (the card's own config must have update_multi:true, set at send
// time). Used to keep a previously-sent weekly roster card in sync with
// mid-week schedule changes without posting a new message.
func PatchFeishuCardMessage(messageID string, card map[string]interface{}) error {
	appID := os.Getenv("FEISHU_APP_ID")
	appSecret := os.Getenv("FEISHU_APP_SECRET")
	if appID == "" || appSecret == "" {
		return fmt.Errorf("FEISHU_APP_ID or FEISHU_APP_SECRET not set")
	}

	contentBytes, err := json.Marshal(card)
	if err != nil {
		return fmt.Errorf("failed to marshal card: %w", err)
	}

	client := lark.NewClient(appID, appSecret)
	req := larkim.NewPatchMessageReqBuilder().
		MessageId(messageID).
		Body(larkim.NewPatchMessageReqBodyBuilder().
			Content(string(contentBytes)).
			Build()).
		Build()

	resp, err := client.Im.Message.Patch(context.Background(), req)
	if err != nil {
		return err
	}
	if !resp.Success() {
		return fmt.Errorf("failed to patch feishu card: code=%d, msg=%s", resp.Code, resp.Msg)
	}

	return nil
}

func sendFeishuText(receiveIDType, receiveID, msg string) error {
	appID := os.Getenv("FEISHU_APP_ID")
	appSecret := os.Getenv("FEISHU_APP_SECRET")
	if appID == "" || appSecret == "" {
		return fmt.Errorf("FEISHU_APP_ID or FEISHU_APP_SECRET not set")
	}

	contentMap := map[string]string{"text": msg}
	contentBytes, err := json.Marshal(contentMap)
	if err != nil {
		return fmt.Errorf("failed to marshal msg: %w", err)
	}

	client := lark.NewClient(appID, appSecret)
	req := larkim.NewCreateMessageReqBuilder().
		ReceiveIdType(receiveIDType).
		Body(larkim.NewCreateMessageReqBodyBuilder().
			ReceiveId(receiveID).
			MsgType("text").
			Content(string(contentBytes)).
			Build()).
		Build()

	resp, err := client.Im.Message.Create(context.Background(), req)
	if err != nil {
		return err
	}

	if !resp.Success() {
		return fmt.Errorf("failed to send feishu message: code=%d, msg=%s", resp.Code, resp.Msg)
	}

	return nil
}
