package photos

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

type Client struct {
	baseURL    string
	httpClient *http.Client
}

func NewClient(baseURL string) *Client {
	return &Client{
		baseURL: baseURL,
		httpClient: &http.Client{
			Timeout: 60 * time.Second,
		},
	}
}

type Asset struct {
	ID           string `json:"id"`
	Type         string `json:"type"`
	OriginalPath string `json:"originalPath"`
	OriginalName string `json:"originalFileName"`
	MimeType     string `json:"originalMimeType"`
	FileSize     int64  `json:"fileSizeInByte"`
	CreatedAt    string `json:"fileCreatedAt"`
	IsFavorite   bool   `json:"isFavorite"`
	ThumbHash    string `json:"thumbhash"`
	ExifInfo     struct {
		FileSizeInByte int64 `json:"fileSizeInByte"`
	} `json:"exifInfo,omitempty"`
}

type Album struct {
	ID          string `json:"id"`
	Name        string `json:"albumName"`
	Description string `json:"description,omitempty"`
	AssetCount  int    `json:"assetCount"`
	CreatedAt   string `json:"createdAt"`
	UpdatedAt   string `json:"updatedAt,omitempty"`
}

const assetsPageSize = 200

func (c *Client) GetAllAssets(ctx context.Context, apiKey string) ([]Asset, error) {
	var all []Asset
	page := 1
	for {
		batch, err := c.GetAssets(ctx, apiKey, page, assetsPageSize)
		if err != nil {
			return nil, err
		}
		all = append(all, batch...)
		if len(batch) < assetsPageSize {
			break
		}
		page++
	}
	return all, nil
}

func (c *Client) GetAssets(ctx context.Context, apiKey string, page, size int) ([]Asset, error) {
	url := fmt.Sprintf("%s/assets?page=%d&size=%d", c.baseURL, page, size)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("x-api-key", apiKey)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		io.Copy(io.Discard, resp.Body)
		return nil, &HTTPStatusError{Operation: "get assets", StatusCode: resp.StatusCode}
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	var assets []Asset
	trimmed := bytes.TrimSpace(body)
	switch {
	case len(trimmed) == 0:
		return []Asset{}, nil
	case trimmed[0] == '[':
		if err := json.Unmarshal(trimmed, &assets); err != nil {
			return nil, err
		}
	default:
		var wrapped struct {
			Items  []Asset `json:"items"`
			Assets []Asset `json:"assets"`
		}
		if err := json.Unmarshal(trimmed, &wrapped); err != nil {
			return nil, err
		}
		if len(wrapped.Items) > 0 {
			assets = wrapped.Items
		} else {
			assets = wrapped.Assets
		}
	}
	for i := range assets {
		if assets[i].FileSize == 0 {
			assets[i].FileSize = assets[i].ExifInfo.FileSizeInByte
		}
	}
	return assets, nil
}

func (c *Client) GetAlbums(ctx context.Context, apiKey string) ([]Album, error) {
	url := fmt.Sprintf("%s/albums", c.baseURL)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("x-api-key", apiKey)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		io.Copy(io.Discard, resp.Body)
		return nil, &HTTPStatusError{Operation: "get albums", StatusCode: resp.StatusCode}
	}

	var albums []Album
	if err := json.NewDecoder(resp.Body).Decode(&albums); err != nil {
		return nil, err
	}
	return albums, nil
}

func (c *Client) UploadAsset(ctx context.Context, apiKey string, body io.Reader, contentType string) (string, error) {
	url := fmt.Sprintf("%s/assets", c.baseURL)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, body)
	if err != nil {
		return "", err
	}
	req.Header.Set("x-api-key", apiKey)
	req.Header.Set("Content-Type", contentType)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
		io.Copy(io.Discard, resp.Body)
		return "", &HTTPStatusError{Operation: "upload asset", StatusCode: resp.StatusCode}
	}

	var result struct {
		ID      string `json:"id"`
		AssetID string `json:"assetId"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return "", err
	}
	if result.ID != "" {
		return result.ID, nil
	}
	return result.AssetID, nil
}

func (c *Client) GetAssetThumbnail(ctx context.Context, apiKey, assetID string) (io.ReadCloser, string, error) {
	url := fmt.Sprintf("%s/assets/%s/thumbnail", c.baseURL, assetID)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, "", err
	}
	req.Header.Set("x-api-key", apiKey)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, "", err
	}
	if resp.StatusCode != http.StatusOK {
		defer resp.Body.Close()
		io.Copy(io.Discard, resp.Body)
		return nil, "", &HTTPStatusError{Operation: "get asset thumbnail", StatusCode: resp.StatusCode}
	}

	return resp.Body, resp.Header.Get("Content-Type"), nil
}

func (c *Client) DeleteAsset(ctx context.Context, apiKey, assetID string) error {
	url := fmt.Sprintf("%s/assets/%s", c.baseURL, assetID)
	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, nil)
	if err != nil {
		return err
	}
	req.Header.Set("x-api-key", apiKey)

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent {
		io.Copy(io.Discard, resp.Body)
		return &HTTPStatusError{Operation: "delete asset", StatusCode: resp.StatusCode}
	}
	return nil
}
