package feichibridge

import (
	"context"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
	"sync"
	"time"
)

type TargetConfig struct {
	Address    string
	PlatformID string
	Username   string
	Password   string
	Timeout    time.Duration
}

type Target struct {
	mu      sync.Mutex
	config  TargetConfig
	state   *StateStore
	dialer  net.Dialer
	conn    net.Conn
	lastACK time.Time
}

func NewTarget(config TargetConfig, state *StateStore) (*Target, error) {
	if strings.TrimSpace(config.Address) == "" {
		return nil, errors.New("GB/T 32960 target address is required")
	}
	if len(config.PlatformID) != 17 {
		return nil, fmt.Errorf("target platform ID must be exactly 17 bytes")
	}
	if state == nil {
		return nil, errors.New("state store is required")
	}
	if config.Timeout <= 0 {
		config.Timeout = 10 * time.Second
	}
	return &Target{
		config: config,
		state:  state,
		dialer: net.Dialer{Timeout: config.Timeout, KeepAlive: 30 * time.Second},
	}, nil
}

func (t *Target) Send(ctx context.Context, frame []byte) error {
	t.mu.Lock()
	defer t.mu.Unlock()
	var lastErr error
	for attempt := 0; attempt < 2; attempt++ {
		if err := t.ensureConnected(ctx); err != nil {
			lastErr = err
			t.closeLocked()
			continue
		}
		if err := t.sendAndACK(ctx, frame); err != nil {
			lastErr = err
			t.closeLocked()
			continue
		}
		t.lastACK = time.Now()
		return nil
	}
	return fmt.Errorf("send GB/T 32960 frame after reconnect: %w", lastErr)
}

func (t *Target) Connect(ctx context.Context) error {
	t.mu.Lock()
	defer t.mu.Unlock()
	if err := t.ensureConnected(ctx); err != nil {
		t.closeLocked()
		return err
	}
	return nil
}

func (t *Target) Close() error {
	t.mu.Lock()
	defer t.mu.Unlock()
	if t.conn == nil {
		return nil
	}
	err := t.conn.Close()
	t.conn = nil
	return err
}

func (t *Target) LastACK() time.Time {
	t.mu.Lock()
	defer t.mu.Unlock()
	return t.lastACK
}

func (t *Target) ensureConnected(ctx context.Context) error {
	if t.conn != nil {
		return nil
	}
	conn, err := t.dialer.DialContext(ctx, "tcp", t.config.Address)
	if err != nil {
		return fmt.Errorf("dial target %s: %w", t.config.Address, err)
	}
	t.conn = conn
	serial, err := t.state.NextPlatformSerial()
	if err != nil {
		return fmt.Errorf("allocate platform login serial: %w", err)
	}
	login, err := LoginFrame(t.config.PlatformID, t.config.Username, t.config.Password, serial, time.Now())
	if err != nil {
		return err
	}
	if err := t.sendAndACK(ctx, login); err != nil {
		return fmt.Errorf("GB/T 32960 platform login: %w", err)
	}
	t.lastACK = time.Now()
	return nil
}

func (t *Target) sendAndACK(ctx context.Context, frame []byte) error {
	if t.conn == nil {
		return errors.New("target connection is closed")
	}
	deadline := time.Now().Add(t.config.Timeout)
	if contextDeadline, ok := ctx.Deadline(); ok && contextDeadline.Before(deadline) {
		deadline = contextDeadline
	}
	if err := t.conn.SetDeadline(deadline); err != nil {
		return err
	}
	if err := writeFull(t.conn, frame); err != nil {
		return fmt.Errorf("write target frame: %w", err)
	}
	response, err := readFrame(t.conn)
	if err != nil {
		return fmt.Errorf("read target ACK: %w", err)
	}
	if response[2] != frame[2] {
		return fmt.Errorf("target ACK command mismatch: got 0x%02X want 0x%02X", response[2], frame[2])
	}
	if response[3] != 0x01 {
		return fmt.Errorf("target rejected command 0x%02X with response 0x%02X", response[2], response[3])
	}
	if string(response[4:21]) != string(frame[4:21]) {
		return errors.New("target ACK identifier mismatch")
	}
	return nil
}

func (t *Target) closeLocked() {
	if t.conn != nil {
		_ = t.conn.Close()
		t.conn = nil
	}
}

func readFrame(reader io.Reader) ([]byte, error) {
	header := make([]byte, 24)
	if _, err := io.ReadFull(reader, header); err != nil {
		return nil, err
	}
	if (header[0] != '#' || header[1] != '#') && (header[0] != '$' || header[1] != '$') {
		return nil, fmt.Errorf("bad GB/T 32960 ACK start %q", header[:2])
	}
	bodyLength := int(binary.BigEndian.Uint16(header[22:24]))
	tail := make([]byte, bodyLength+1)
	if _, err := io.ReadFull(reader, tail); err != nil {
		return nil, err
	}
	frame := append(header, tail...)
	if got, want := bcc(frame[2:len(frame)-1]), frame[len(frame)-1]; got != want {
		return nil, fmt.Errorf("bad GB/T 32960 ACK BCC: got 0x%02X want 0x%02X", got, want)
	}
	return frame, nil
}

func writeFull(writer io.Writer, value []byte) error {
	for len(value) > 0 {
		written, err := writer.Write(value)
		if err != nil {
			return err
		}
		if written == 0 {
			return io.ErrShortWrite
		}
		value = value[written:]
	}
	return nil
}
