package delima

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

var (
	ErrNotConfigured = errors.New("delima: client not configured")
	ErrUserNotFound  = errors.New("delima: user not found")
)

type Client struct {
	BaseURL string
	Token   string
	HTTP    *http.Client
}

func New(baseURL, token string) *Client {
	return &Client{
		BaseURL: strings.TrimRight(baseURL, "/"),
		Token:   token,
		HTTP:    &http.Client{Timeout: 30 * time.Second},
	}
}

func (c *Client) Configured() bool {
	return c.BaseURL != "" && c.Token != ""
}

type Role struct {
	Text string `json:"text"`
}

type Organization struct {
	Code     string `json:"code"`
	Name     string `json:"name"`
	Email    string `json:"email"`
	Address  string `json:"address"`
	City     string `json:"city"`
	Postcode string `json:"postcode"`
	District string `json:"district"`
	State    string `json:"state"`
	Type     string `json:"type"`
}

type Classroom struct {
	Code string `json:"code"`
	Name string `json:"name"`
}

type User struct {
	ID           flexString    `json:"id"`
	Email        string        `json:"email"`
	Role         Role          `json:"role"`
	Organization *Organization `json:"organization"`
	Classroom    *Classroom    `json:"classroom"`
}

func (u *User) Valid() bool {
	return u != nil && u.ID != "" && u.Email != ""
}

func (c *Client) User(ctx context.Context, email string) (*User, error) {
	if !c.Configured() {
		return nil, ErrNotConfigured
	}

	endpoint := c.BaseURL + "/user/" + url.PathEscape(email)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("X-DELIMA-TOKEN", c.Token)
	req.Header.Set("Accept", "application/json")

	resp, err := c.HTTP.Do(req)
	if err != nil {
		return nil, err
	}
	defer func() { _ = resp.Body.Close() }()

	body, err := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("delima: request failed: %s", resp.Status)
	}

	user := &User{}
	if err := json.Unmarshal(body, user); err != nil {
		return nil, fmt.Errorf("delima: invalid json: %w", err)
	}
	if !user.Valid() {
		return nil, ErrUserNotFound
	}
	return user, nil
}

type flexString string

func (f *flexString) UnmarshalJSON(data []byte) error {
	data = trimJSON(data)
	if len(data) == 0 || string(data) == "null" {
		*f = ""
		return nil
	}
	var s string
	if err := json.Unmarshal(data, &s); err == nil {
		*f = flexString(s)
		return nil
	}
	var n json.Number
	if err := json.Unmarshal(data, &n); err == nil {
		*f = flexString(n.String())
		return nil
	}
	return fmt.Errorf("delima: cannot decode id %s", string(data))
}

func (f flexString) String() string {
	return string(f)
}

func trimJSON(data []byte) []byte {
	return []byte(strings.TrimSpace(string(data)))
}
