package line

import (
	"context"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"strings"
	"sync"
	"time"

	"github.com/chenhg5/cc-connect/core"

	"github.com/line/line-bot-sdk-go/v8/linebot/messaging_api"
	"github.com/line/line-bot-sdk-go/v8/linebot/webhook"
)

func init() {
	core.RegisterPlatform("line", New)
}

// replyContext stores the user/group ID for push messages.
// We use PushMessage instead of ReplyMessage because reply tokens
// expire in ~1 minute, which is too short for AI agent processing.
type replyContext struct {
	targetID   string
	targetType string // "user" or "group" or "room"
}

type Platform struct {
	channelSecret string
	channelToken  string
	allowFrom     string
	port          string
	callbackPath  string
	bot           *messaging_api.MessagingApiAPI
	server        *http.Server
	handler       core.MessageHandler
	userNameCache sync.Map // userID -> display name
	groupNameCache sync.Map // groupID -> group name
}

func New(opts map[string]any) (core.Platform, error) {
	secret, _ := opts["channel_secret"].(string)
	token, _ := opts["channel_token"].(string)
	allowFrom, _ := opts["allow_from"].(string)
	if secret == "" || token == "" {
		return nil, fmt.Errorf("line: channel_secret and channel_token are required")
	}

	port, _ := opts["port"].(string)
	if port == "" {
		port = "8080"
	}
	path, _ := opts["callback_path"].(string)
	if path == "" {
		path = "/callback"
	}

	core.CheckAllowFrom("line", allowFrom)
	return &Platform{
		channelSecret: secret,
		channelToken:  token,
		allowFrom:     allowFrom,
		port:          port,
		callbackPath:  path,
	}, nil
}

func (p *Platform) Name() string { return "line" }

func (p *Platform) Start(handler core.MessageHandler) error {
	p.handler = handler

	bot, err := messaging_api.NewMessagingApiAPI(p.channelToken)
	if err != nil {
		return fmt.Errorf("line: create api client: %w", err)
	}
	p.bot = bot

	mux := http.NewServeMux()
	mux.HandleFunc(p.callbackPath, p.webhookHandler)

	p.server = &http.Server{
		Addr:    ":" + p.port,
		Handler: mux,
	}

	go func() {
		slog.Info("line: webhook server listening", "port", p.port, "path", p.callbackPath)
		if err := p.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			slog.Error("line: server error", "error", err)
		}
	}()

	return nil
}

func (p *Platform) webhookHandler(w http.ResponseWriter, r *http.Request) {
	cb, err := webhook.ParseRequest(p.channelSecret, r)
	if err != nil {
		slog.Error("line: parse webhook failed", "error", err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	w.WriteHeader(http.StatusOK)

	for _, event := range cb.Events {
		e, ok := event.(webhook.MessageEvent)
		if !ok {
			continue
		}

		if e.Timestamp > 0 {
			msgTime := time.Unix(e.Timestamp/1000, (e.Timestamp%1000)*int64(time.Millisecond))
			if core.IsOldMessage(msgTime) {
				slog.Debug("line: ignoring old message after restart", "timestamp", e.Timestamp)
				continue
			}
		}

		targetID, targetType, userID := extractSource(e.Source)
		if !core.AllowList(p.allowFrom, userID) {
			slog.Debug("line: message from unauthorized user", "user", userID)
			continue
		}
		sessionKey := fmt.Sprintf("line:%s", targetID)
		rctx := replyContext{targetID: targetID, targetType: targetType}

		chatName := ""
		if targetType == "group" {
			chatName = p.resolveGroupName(targetID)
		}

		switch m := e.Message.(type) {
		case webhook.TextMessageContent:
			slog.Debug("line: message received", "user", userID, "text_len", len(m.Text))
			p.handler(p, &core.Message{
				SessionKey: sessionKey, Platform: "line",
				MessageID: m.Id,
				UserID: userID, UserName: p.resolveUserName(userID),
				ChatName: chatName,
				Content: m.Text, ReplyCtx: rctx,
			})

		case webhook.ImageMessageContent:
			slog.Debug("line: image received", "user", userID)
			imgData, err := p.downloadContent(m.Id)
			if err != nil {
				slog.Error("line: download image failed", "error", err)
				continue
			}
			p.handler(p, &core.Message{
				SessionKey: sessionKey, Platform: "line",
				MessageID: m.Id,
				UserID: userID, UserName: p.resolveUserName(userID),
				ChatName: chatName,
				Images:  []core.ImageAttachment{{MimeType: "image/jpeg", Data: imgData}},
				ReplyCtx: rctx,
			})

		case webhook.AudioMessageContent:
			slog.Debug("line: audio received", "user", userID)
			audioData, err := p.downloadContent(m.Id)
			if err != nil {
				slog.Error("line: download audio failed", "error", err)
				continue
			}
			dur := 0
			if m.Duration > 0 {
				dur = int(m.Duration / 1000)
			}
			p.handler(p, &core.Message{
				SessionKey: sessionKey, Platform: "line",
				MessageID: m.Id,
				UserID: userID, UserName: p.resolveUserName(userID),
				ChatName: chatName,
				Audio: &core.AudioAttachment{
					MimeType: "audio/m4a",
					Data:     audioData,
					Format:   "m4a",
					Duration: dur,
				},
				ReplyCtx: rctx,
			})

		default:
			slog.Debug("line: ignoring unsupported message type")
		}
	}
}

func (p *Platform) resolveUserName(userID string) string {
	if cached, ok := p.userNameCache.Load(userID); ok {
		return cached.(string)
	}
	profile, err := p.bot.GetProfile(userID)
	if err != nil {
		slog.Debug("line: resolve user name failed", "user", userID, "error", err)
		return userID
	}
	name := profile.DisplayName
	if name == "" {
		name = userID
	}
	p.userNameCache.Store(userID, name)
	return name
}

func (p *Platform) resolveGroupName(groupID string) string {
	if cached, ok := p.groupNameCache.Load(groupID); ok {
		return cached.(string)
	}
	summary, err := p.bot.GetGroupSummary(groupID)
	if err != nil {
		slog.Debug("line: resolve group name failed", "group_id", groupID, "error", err)
		return groupID
	}
	name := summary.GroupName
	if name == "" {
		return groupID
	}
	p.groupNameCache.Store(groupID, name)
	return name
}

func extractSource(src webhook.SourceInterface) (targetID, targetType, userID string) {
	switch s := src.(type) {
	case webhook.UserSource:
		return s.UserId, "user", s.UserId
	case webhook.GroupSource:
		return s.GroupId, "group", s.UserId
	case webhook.RoomSource:
		return s.RoomId, "room", s.UserId
	default:
		return "unknown", "unknown", "unknown"
	}
}

func (p *Platform) downloadContent(messageID string) ([]byte, error) {
	url := fmt.Sprintf("https://api-data.line.me/v2/bot/message/%s/content", messageID)
	req, _ := http.NewRequest("GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+p.channelToken)
	resp, err := core.HTTPClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

func (p *Platform) Reply(ctx context.Context, rctx any, content string) error {
	rc, ok := rctx.(replyContext)
	if !ok {
		return fmt.Errorf("line: invalid reply context type %T", rctx)
	}

	if content == "" {
		return nil
	}

	content = core.StripMarkdown(content)

	// LINE text message limit is 5000 characters
	messages := splitMessage(content, 5000)
	for _, text := range messages {
		_, err := p.bot.PushMessage(
			&messaging_api.PushMessageRequest{
				To: rc.targetID,
				Messages: []messaging_api.MessageInterface{
					messaging_api.TextMessage{
						Text: text,
					},
				},
			}, "",
		)
		if err != nil {
			return fmt.Errorf("line: push message: %w", err)
		}
	}
	return nil
}

// Send sends a new message (same as Reply for LINE)
func (p *Platform) Send(ctx context.Context, rctx any, content string) error {
	return p.Reply(ctx, rctx, content)
}

func splitMessage(s string, maxLen int) []string {
	if len(s) <= maxLen {
		return []string{s}
	}
	var parts []string
	runes := []rune(s)
	for len(runes) > 0 {
		end := maxLen
		if end > len(runes) {
			end = len(runes)
		}
		parts = append(parts, string(runes[:end]))
		runes = runes[end:]
	}
	return parts
}

func (p *Platform) ReconstructReplyCtx(sessionKey string) (any, error) {
	// line:{targetID} (user or group)
	parts := strings.SplitN(sessionKey, ":", 2)
	if len(parts) < 2 || parts[0] != "line" {
		return nil, fmt.Errorf("line: invalid session key %q", sessionKey)
	}
	return replyContext{targetID: parts[1], targetType: "user"}, nil
}

func (p *Platform) Stop() error {
	if p.server != nil {
		return p.server.Shutdown(context.Background())
	}
	return nil
}
