package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/cookiejar"
	"strings"
	"time"
)

// APIClient wraps HTTP calls to the dumpster server.
type APIClient struct {
	BaseURL      string
	HTTP         *http.Client
	SessionToken string
}

// NewAPIClient creates an API client with a cookie jar for session auth.
func NewAPIClient(baseURL string) *APIClient {
	jar, _ := cookiejar.New(nil)
	return &APIClient{
		BaseURL: strings.TrimRight(baseURL, "/"),
		HTTP: &http.Client{
			Jar:     jar,
			Timeout: 15 * time.Second,
		},
	}
}

// ---- Auth ----

type LoginRequest struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

type LoginResponse struct {
	ID string `json:"id"`
}

// Login authenticates and stores the session cookie.
func (c *APIClient) Login(email, password string) (*LoginResponse, error) {
	body, _ := json.Marshal(LoginRequest{Email: email, Password: password})
	resp, err := c.HTTP.Post(c.BaseURL+"/api/v1/auth/login/password", "application/json", bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("login request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		b, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("login failed (%d): %s", resp.StatusCode, string(b))
	}

	// Extract session token from cookie
	for _, cookie := range resp.Cookies() {
		if cookie.Name == "dumpster_session" {
			c.SessionToken = cookie.Value
			break
		}
	}

	var lr LoginResponse
	if err := json.NewDecoder(resp.Body).Decode(&lr); err != nil {
		return nil, fmt.Errorf("decode login: %w", err)
	}
	return &lr, nil
}

// SetToken sets the session cookie directly (for pre-authenticated sessions).
func (c *APIClient) SetToken(token string) {
	c.SessionToken = token
	// Set cookie on all requests to the base URL
	// We do this by setting up a cookie manually
}

// doRequest performs an authenticated request using the session cookie.
func (c *APIClient) doRequest(method, path string, body interface{}) ([]byte, error) {
	var bodyReader io.Reader
	if body != nil {
		data, err := json.Marshal(body)
		if err != nil {
			return nil, fmt.Errorf("marshal body: %w", err)
		}
		bodyReader = bytes.NewReader(data)
	}

	req, err := http.NewRequest(method, c.BaseURL+path, bodyReader)
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	// Add session cookie
	if c.SessionToken != "" {
		req.AddCookie(&http.Cookie{
			Name:  "dumpster_session",
			Value: c.SessionToken,
		})
	}

	resp, err := c.HTTP.Do(req)
	if err != nil {
		return nil, fmt.Errorf("do request: %w", err)
	}
	defer resp.Body.Close()

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("read body: %w", err)
	}

	if resp.StatusCode >= 400 {
		return nil, fmt.Errorf("API error (%d): %s", resp.StatusCode, string(data))
	}

	return data, nil
}

// ---- Data types ----

type UserProfile struct {
	ID          string `json:"id"`
	Username    string `json:"username"`
	DisplayName string `json:"display_name"`
	Email       string `json:"email"`
	Avatar      string `json:"avatar"`
	Bio         string `json:"bio"`
	AccentColor string `json:"accent_color"`
	Status      string `json:"status"`
	StatusText  string `json:"status_text"`
	CreatedAt   string `json:"created_at"`
}

type Server struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Icon      string `json:"icon"`
	OwnerID   string `json:"owner_id"`
	CreatedAt string `json:"created_at"`
}

type Channel struct {
	ID        string `json:"id"`
	ServerID  string `json:"server_id"`
	Name      string `json:"name"`
	Type      string `json:"type"`
	Category  string `json:"category"`
	Position  int    `json:"position"`
	CreatedAt string `json:"created_at"`
}

type Message struct {
	ID          string  `json:"id"`
	ChannelID   string  `json:"channel_id"`
	AuthorID    string  `json:"author_id"`
	AuthorName  string  `json:"author_username"`
	DisplayName *string `json:"author_display_name"`
	Content     string  `json:"content"`
	EditedAt    *string `json:"edited_at"`
	CreatedAt   string  `json:"created_at"`
}

type VoiceTokenResponse struct {
	Token      string `json:"token"`
	LiveKitURL string `json:"livekit_url"`
}

// ---- API methods ----

func (c *APIClient) GetMe() (*UserProfile, error) {
	data, err := c.doRequest("GET", "/api/v1/auth/me", nil)
	if err != nil {
		return nil, err
	}
	var user UserProfile
	if err := json.Unmarshal(data, &user); err != nil {
		return nil, fmt.Errorf("decode me: %w", err)
	}
	return &user, nil
}

func (c *APIClient) GetServers() ([]Server, error) {
	data, err := c.doRequest("GET", "/api/v1/servers", nil)
	if err != nil {
		return nil, err
	}
	var servers []Server
	if err := json.Unmarshal(data, &servers); err != nil {
		return nil, fmt.Errorf("decode servers: %w", err)
	}
	return servers, nil
}

func (c *APIClient) GetChannels(serverID string) ([]Channel, error) {
	data, err := c.doRequest("GET", "/api/v1/channels?server_id="+serverID, nil)
	if err != nil {
		return nil, err
	}
	var channels []Channel
	if err := json.Unmarshal(data, &channels); err != nil {
		return nil, fmt.Errorf("decode channels: %w", err)
	}
	return channels, nil
}

func (c *APIClient) GetMessages(channelID string, limit int, before string) ([]Message, error) {
	path := fmt.Sprintf("/api/v1/channels/%s/messages?limit=%d", channelID, limit)
	if before != "" {
		path += "&before=" + before
	}
	data, err := c.doRequest("GET", path, nil)
	if err != nil {
		return nil, err
	}
	var messages []Message
	if err := json.Unmarshal(data, &messages); err != nil {
		return nil, fmt.Errorf("decode messages: %w", err)
	}
	return messages, nil
}

func (c *APIClient) SendMessage(channelID, content string) (*Message, error) {
	data, err := c.doRequest("POST", "/api/v1/channels/"+channelID+"/messages", map[string]string{"content": content})
	if err != nil {
		return nil, err
	}
	var msg Message
	if err := json.Unmarshal(data, &msg); err != nil {
		return nil, fmt.Errorf("decode message: %w", err)
	}
	return &msg, nil
}

func (c *APIClient) GetVoiceToken(roomName string) (*VoiceTokenResponse, error) {
	data, err := c.doRequest("POST", "/api/v1/voice/token", map[string]string{"room_name": roomName})
	if err != nil {
		return nil, err
	}
	var resp VoiceTokenResponse
	if err := json.Unmarshal(data, &resp); err != nil {
		return nil, fmt.Errorf("decode voice token: %w", err)
	}
	return &resp, nil
}
