package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
	"strings"
	"sync"
)

type Player struct {
	ID   string
	Conn net.Conn
}

type GameRoom struct {
	White *Player
	Black *Player
	Game  *Game
	Mutex sync.Mutex
}

type Server struct {
	Listener  net.Listener
	Waiting   []*Player
	Rooms     map[string]*GameRoom
	RoomMutex sync.Mutex
	RoomCount int
}

func NewServer() *Server {
	return &Server{
		Rooms: make(map[string]*GameRoom),
	}
}

func (s *Server) Start(addr string) error {
	var err error
	s.Listener, err = net.Listen("tcp", addr)
	if err != nil {
		return fmt.Errorf("failed to listen on %s: %v", addr, err)
	}
	fmt.Printf("=== CHESS SERVER ===\n")
	fmt.Printf("Listening on %s\n", addr)
	fmt.Printf("Type 'status' to show active games, 'quit' to exit.\n\n")
	go func() {
		for {
			conn, err := s.Listener.Accept()
			if err != nil {
				return
			}
			go s.handleClient(conn)
		}
	}()
	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		switch line {
		case "status":
			s.RoomMutex.Lock()
			fmt.Printf("Active games: %d | Waiting: %d\n", len(s.Rooms), len(s.Waiting))
			for _, room := range s.Rooms {
				fmt.Printf("  %s vs %s (move %d)\n", room.White.ID, room.Black.ID, room.Game.MoveNum)
			}
			s.RoomMutex.Unlock()
		case "quit", "q":
			s.Listener.Close()
			fmt.Println("Server shutting down.")
			return nil
		case "":
			continue
		default:
			fmt.Printf("Unknown: %s (try 'status' or 'quit')\n", line)
		}
	}
	return nil
}

func (s *Server) handleClient(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)
	playerID := fmt.Sprintf("Player-%d", s.RoomCount+1)
	fmt.Printf("[+] %s connected (%s)\n", playerID, conn.RemoteAddr())
	player := &Player{ID: playerID, Conn: conn}
	fmt.Fprintln(conn, "CHESS-SERVER v1.0")
	line, err := reader.ReadString('\n')
	if err != nil {
		return
	}
	if strings.TrimSpace(line) != "HELLO" {
		fmt.Fprintln(conn, "ERROR expected HELLO")
		return
	}
	s.RoomMutex.Lock()
	s.Waiting = append(s.Waiting, player)
	foundOpponent := false
	for i, p := range s.Waiting {
		if p == player {
			continue
		}
		s.Waiting = append(s.Waiting[:i], s.Waiting[i+1:]...)
		foundOpponent = true
		s.RoomCount++
		roomID := fmt.Sprintf("game-%d", s.RoomCount)
		room := &GameRoom{
			White: player,
			Black: p,
			Game:  NewGame(),
		}
		s.Rooms[roomID] = room
		fmt.Fprintf(player.Conn, "MATCHED %s WHITE\n", roomID)
		fmt.Fprintf(p.Conn, "MATCHED %s BLACK\n", roomID)
		boardStr := room.Game.Render()
		fmt.Fprintf(player.Conn, "BOARD\n%s\n", boardStr)
		fmt.Fprintf(p.Conn, "BOARD\n%s\n", boardStr)
		go s.gameLoop(room)
		break
	}
	s.RoomMutex.Unlock()
	if !foundOpponent {
		fmt.Fprintln(player.Conn, "WAITING")
		fmt.Printf("  %s waiting for opponent...\n", playerID)
	}
}

func (s *Server) gameLoop(room *GameRoom) {
	whiteReader := bufio.NewReader(room.White.Conn)
	blackReader := bufio.NewReader(room.Black.Conn)
	fmt.Printf("[=] Game: %s (W) vs %s (B)\n", room.White.ID, room.Black.ID)
	whiteCh := make(chan string, 4)
	blackCh := make(chan string, 4)
	doneCh := make(chan struct{})

	go func() {
		defer close(whiteCh)
		for {
			line, err := whiteReader.ReadString('\n')
			if err != nil {
				close(doneCh)
				return
			}
			line = strings.TrimSpace(line)
			if line == "RESIGN" {
				room.Mutex.Lock()
				room.Game.Over = true
				room.Game.Result = BlackWins
				room.Mutex.Unlock()
				close(doneCh)
				return
			}
			if line == "QUIT" {
				close(doneCh)
				return
			}
			whiteCh <- line
		}
	}()
	go func() {
		defer close(blackCh)
		for {
			line, err := blackReader.ReadString('\n')
			if err != nil {
				close(doneCh)
				return
			}
			line = strings.TrimSpace(line)
			if line == "RESIGN" {
				room.Mutex.Lock()
				room.Game.Over = true
				room.Game.Result = WhiteWins
				room.Mutex.Unlock()
				close(doneCh)
				return
			}
			if line == "QUIT" {
				close(doneCh)
				return
			}
			blackCh <- line
		}
	}()

	for {
		select {
		case <-doneCh:
			s.finishRoom(room)
			return
		default:
		}
		room.Mutex.Lock()
		if room.Game.Over {
			room.Mutex.Unlock()
			s.sendBoard(room)
			s.finishRoom(room)
			return
		}
		var moveStr string
		var ok bool
		if room.Game.Turn == White {
			select {
			case moveStr, ok = <-whiteCh:
				if !ok {
					room.Mutex.Unlock()
					s.finishRoom(room)
					return
				}
			case <-doneCh:
				room.Mutex.Unlock()
				s.finishRoom(room)
				return
			}
		} else {
			select {
			case moveStr, ok = <-blackCh:
				if !ok {
					room.Mutex.Unlock()
					s.finishRoom(room)
					return
				}
			case <-doneCh:
				room.Mutex.Unlock()
				s.finishRoom(room)
				return
			}
		}
		_, err := room.Game.MakeMove(moveStr)
		over := room.Game.Over
		room.Mutex.Unlock()
		if err != nil {
			if room.Game.Turn == White {
				fmt.Fprintf(room.White.Conn, "ERROR %v\n", err)
			} else {
				fmt.Fprintf(room.Black.Conn, "ERROR %v\n", err)
			}
			continue
		}
		fmt.Printf("  %s: %s\n", moveStr, room.Game.Turn)
		s.sendBoard(room)
		if over {
			s.finishRoom(room)
			return
		}
	}
}

func (s *Server) sendBoard(room *GameRoom) {
	boardStr := room.Game.Render()
	fmt.Fprintf(room.White.Conn, "BOARD\n%s\n", boardStr)
	fmt.Fprintf(room.Black.Conn, "BOARD\n%s\n", boardStr)
}

func (s *Server) finishRoom(room *GameRoom) {
	s.RoomMutex.Lock()
	for k, r := range s.Rooms {
		if r == room {
			delete(s.Rooms, k)
			break
		}
	}
	s.RoomMutex.Unlock()
	room.White.Conn.Close()
	room.Black.Conn.Close()
	fmt.Printf("[=] Game ended: %s vs %s\n", room.White.ID, room.Black.ID)
}

