package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"

	"omnisocketgo/cmd/internal/latencylog"
	peerpkg "omnisocketgo/cmd/internal/peer"
	"omnisocketgo/cmd/internal/protocol"
)

func main() {
	peerID := flag.String("id", "peer-a", "peer identity")                               // peer 的标识
	serverAddr := flag.String("server", "127.0.0.1:9000", "server address")              // server 的地址
	targetPeer := flag.String("to", "", "optional target peer for one outgoing message") // 可选的目标 peer 标识
	text := flag.String("text", "", "optional text to send after connecting")            // 可选的文本消息内容
	filePath := flag.String("file", "", "optional file path to send after connecting")
	bindIP := flag.String("bind-ip", "", "optional local source IP used when dialing the server")
	bindDevice := flag.String("bind-device", "", "optional Linux network device used when dialing the server")
	inboxDir := flag.String("inbox-dir", "inbox", "directory used to persist received text and file messages")
	logPath := flag.String("latency-log", "", "optional JSONL file path for latency timestamp logs")
	interactive := flag.Bool("interactive", true, "enable interactive REPL for repeated text/file sends on the same connection")
	flag.Parse()

	clientOptions := make([]peerpkg.Option, 0, 3)
	if *logPath != "" {
		logger, err := latencylog.NewJSONLLogger(*logPath)
		if err != nil {
			log.Fatalf("create latency logger %s: %v", *logPath, err)
		}
		defer logger.Close()
		clientOptions = append(clientOptions, peerpkg.WithLogger(logger))
	}
	if *bindIP != "" {
		clientOptions = append(clientOptions, peerpkg.WithBindIP(*bindIP))
	}
	if *bindDevice != "" {
		clientOptions = append(clientOptions, peerpkg.WithBindDevice(*bindDevice))
	}

	client, err := peerpkg.Dial(*serverAddr, *peerID, clientOptions...)
	if err != nil {
		log.Fatalf("dial server %s: %v", *serverAddr, err)
	}
	defer client.Close()

	log.Printf("connected to %s as %s", *serverAddr, client.ID())

	receiveErr := make(chan error, 1)
	go func() {
		receiveErr <- client.ReceiveLoop(func(msg protocol.Message) error {
			switch msg.Type {
			case protocol.MessageTypeText:
				path, err := client.PersistMessage(msg, *inboxDir)
				if err != nil {
					return err
				}
				log.Printf("received text from %s to %s and persisted to %s", msg.From, msg.To, path)
			case protocol.MessageTypeFile:
				path, err := client.PersistMessage(msg, *inboxDir)
				if err != nil {
					return err
				}
				log.Printf("received file from %s to %s: %s (%d bytes) -> %s", msg.From, msg.To, msg.FileName, len(msg.Body), path)
			case protocol.MessageTypeError:
				log.Printf("received %s from %s to %s: %s", msg.Type, msg.From, msg.To, string(msg.Body))
			default:
				log.Printf("received unexpected message type %s from %s", msg.Type, msg.From)
			}
			return nil
		})
	}()

	if *text != "" && *filePath != "" {
		log.Fatal("only one of -text or -file may be specified")
	}

	if (*text != "" || *filePath != "") && *targetPeer == "" {
		log.Fatal("flag -to is required when sending text or file")
	}

	//如果指定了目标 peer 和文本消息内容，则向目标 peer 发送一条文本消息，如果发送失败，打印错误日志并退出。
	if *targetPeer != "" && *text != "" {
		if err := client.SendText(*targetPeer, *text); err != nil {
			log.Fatalf("send text to %s: %v", *targetPeer, err)
		}
		log.Printf("sent text to %s", *targetPeer)
	}

	if *targetPeer != "" && *filePath != "" {
		if err := client.SendFilePath(*targetPeer, *filePath); err != nil {
			log.Fatalf("send file %s to %s: %v", *filePath, *targetPeer, err)
		}
		log.Printf("sent file %s to %s", *filePath, *targetPeer)
	}

	if *interactive {
		if err := runInteractiveShell(client, os.Stdin, os.Stdout, receiveErr); err != nil {
			log.Printf("interactive shell ended: %v", err)
		}
		return
	}

	if err := <-receiveErr; err != nil {
		log.Printf("receive loop ended: %v", err)
	}
}

func runInteractiveShell(client *peerpkg.Client, in io.Reader, out io.Writer, receiveErr <-chan error) error {
	printInteractiveHelp(out)
	lines, inputErr := readInteractiveLines(in, out, fmt.Sprintf("%s> ", client.ID()))

	for {
		select {
		case err := <-receiveErr:
			return err
		case line, ok := <-lines:
			if !ok {
				return <-inputErr
			}

			command, err := parseInteractiveCommand(line)
			if err != nil {
				if err == errEmptyInteractiveCommand {
					continue
				}
				log.Printf("interactive command error: %v", err)
				continue
			}

			switch command.name {
			case interactiveCommandHelp:
				printInteractiveHelp(out)
			case interactiveCommandQuit:
				return nil
			case interactiveCommandText:
				if err := client.SendText(command.to, command.value); err != nil {
					log.Printf("send text to %s: %v", command.to, err)
					continue
				}
				log.Printf("sent text to %s", command.to)
			case interactiveCommandFile:
				if err := client.SendFilePath(command.to, command.value); err != nil {
					log.Printf("send file %s to %s: %v", command.value, command.to, err)
					continue
				}
				log.Printf("sent file %s to %s", command.value, command.to)
			}
		}
	}
}

func readInteractiveLines(in io.Reader, out io.Writer, prompt string) (<-chan string, <-chan error) {
	lines := make(chan string)
	errs := make(chan error, 1)

	go func() {
		defer close(lines)

		scanner := bufio.NewScanner(in)
		scanner.Buffer(make([]byte, 0, 1024), 1024*1024)

		for {
			if _, err := fmt.Fprint(out, prompt); err != nil {
				errs <- err
				return
			}
			if !scanner.Scan() {
				errs <- scanner.Err()
				return
			}
			lines <- scanner.Text()
		}
	}()

	return lines, errs
}
