package clash

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const defaultBaseURL = "https://api.clashofclans.com/v1"

// Client wraps the Clash of Clans REST API.
type Client struct {
	token      string
	httpClient *http.Client
	baseURL    string
}

// MemberDetail combines clan and player information for reporting.
type MemberDetail struct {
	Tag               string
	Name              string
	TownHallLevel     int
	League            string
	Trophies          int
	Donations         int
	DonationsReceived int
	LastSeen          time.Time
}

// NewClient constructs a Clash API client with the provided token.
func NewClient(token string, opts ...Option) *Client {
	c := &Client{
		token: token,
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
		baseURL: defaultBaseURL,
	}

	for _, opt := range opts {
		opt(c)
	}

	return c
}

// Option customises the API client.
type Option func(*Client)

// WithHTTPClient injects a custom HTTP client.
func WithHTTPClient(httpClient *http.Client) Option {
	return func(c *Client) {
		if httpClient != nil {
			c.httpClient = httpClient
		}
	}
}

// WithBaseURL overrides the default API base URL. Useful for testing.
func WithBaseURL(baseURL string) Option {
	return func(c *Client) {
		if baseURL != "" {
			c.baseURL = strings.TrimRight(baseURL, "/")
		}
	}
}

// ClanMembers returns detailed information for current clan members.
func (c *Client) ClanMembers(ctx context.Context, clanTag string) ([]MemberDetail, error) {
	clan, err := c.fetchClan(ctx, clanTag)
	if err != nil {
		return nil, err
	}

	if len(clan.MemberList) == 0 {
		return nil, nil
	}

	members := make([]MemberDetail, 0, len(clan.MemberList))
	for _, member := range clan.MemberList {
		player, err := c.fetchPlayer(ctx, member.Tag)
		if err != nil {
			return nil, fmt.Errorf("fetch player %s: %w", member.Tag, err)
		}

		lastSeen, err := parseLastSeen(player.LastSeen)
		if err != nil {
			lastSeen = time.Time{}
		}

		members = append(members, MemberDetail{
			Tag:               member.Tag,
			Name:              member.Name,
			TownHallLevel:     player.TownHallLevel,
			League:            member.League.Name,
			Trophies:          member.Trophies,
			Donations:         member.Donations,
			DonationsReceived: member.DonationsReceived,
			LastSeen:          lastSeen,
		})
	}

	return members, nil
}

func (c *Client) fetchClan(ctx context.Context, clanTag string) (*clanResponse, error) {
	escapedTag := url.PathEscape(strings.TrimSpace(clanTag))
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/clans/%s", c.baseURL, escapedTag), nil)
	if err != nil {
		return nil, fmt.Errorf("build clan request: %w", err)
	}

	c.applyHeaders(req)

	res, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("execute clan request: %w", err)
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(res.Body, 2048))
		return nil, fmt.Errorf("clan request failed: status=%d body=%s", res.StatusCode, string(body))
	}

	var payload clanResponse
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		return nil, fmt.Errorf("decode clan response: %w", err)
	}

	return &payload, nil
}

func (c *Client) fetchPlayer(ctx context.Context, playerTag string) (*playerResponse, error) {
	escapedTag := url.PathEscape(strings.TrimSpace(playerTag))
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/players/%s", c.baseURL, escapedTag), nil)
	if err != nil {
		return nil, fmt.Errorf("build player request: %w", err)
	}

	c.applyHeaders(req)

	res, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("execute player request: %w", err)
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(res.Body, 2048))
		return nil, fmt.Errorf("player request failed: status=%d body=%s", res.StatusCode, string(body))
	}

	var payload playerResponse
	if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
		return nil, fmt.Errorf("decode player response: %w", err)
	}

	return &payload, nil
}

func (c *Client) applyHeaders(req *http.Request) {
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Authorization", "Bearer "+c.token)
}

func parseLastSeen(value string) (time.Time, error) {
	if value == "" {
		return time.Time{}, fmt.Errorf("empty lastSeen")
	}

	layouts := []string{
		"20060102T150405.000Z",
		time.RFC3339,
	}

	for _, layout := range layouts {
		if t, err := time.Parse(layout, value); err == nil {
			return t, nil
		}
	}

	return time.Time{}, fmt.Errorf("unsupported lastSeen format: %s", value)
}

type clanResponse struct {
	MemberList []clanMember `json:"memberList"`
}

type clanMember struct {
	Tag               string `json:"tag"`
	Name              string `json:"name"`
	Donations         int    `json:"donations"`
	DonationsReceived int    `json:"donationsReceived"`
	Trophies          int    `json:"trophies"`
	League            league `json:"league"`
	Role              string `json:"role"`
	ExpLevel          int    `json:"expLevel"`
	TownHallLevel     int    `json:"townHallLevel"`
}

type league struct {
	Name string `json:"name"`
}

type playerResponse struct {
	Tag           string `json:"tag"`
	Name          string `json:"name"`
	LastSeen      string `json:"lastSeen"`
	TownHallLevel int    `json:"townHallLevel"`
}
