package photos

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

type BulkAssetResult struct {
	ID      string `json:"id"`
	Success bool   `json:"success"`
	Error   string `json:"error,omitempty"`
}

type createAlbumPayload struct {
	AlbumName   string   `json:"albumName"`
	Description string   `json:"description,omitempty"`
	AssetIDs    []string `json:"assetIds,omitempty"`
}

type updateAlbumPayload struct {
	AlbumName   *string `json:"albumName,omitempty"`
	Description *string `json:"description,omitempty"`
}

type bulkIDsPayload struct {
	IDs []string `json:"ids"`
}

func (c *Client) GetAlbum(ctx context.Context, apiKey, albumID string) (Album, error) {
	url := fmt.Sprintf("%s/albums/%s?withoutAssets=true", c.baseURL, albumID)
	var album Album
	if err := c.doJSON(ctx, http.MethodGet, url, apiKey, nil, &album, "get album", http.StatusOK); err != nil {
		return Album{}, err
	}
	return album, nil
}

func (c *Client) CreateAlbum(ctx context.Context, apiKey, name, description string, assetIDs []string) (Album, error) {
	payload := createAlbumPayload{
		AlbumName:   name,
		Description: description,
		AssetIDs:    assetIDs,
	}
	var album Album
	if err := c.doJSON(ctx, http.MethodPost, c.baseURL+"/albums", apiKey, payload, &album, "create album", http.StatusCreated); err != nil {
		return Album{}, err
	}
	return album, nil
}

func (c *Client) UpdateAlbum(ctx context.Context, apiKey, albumID string, name, description *string) (Album, error) {
	payload := updateAlbumPayload{
		AlbumName:   name,
		Description: description,
	}
	url := fmt.Sprintf("%s/albums/%s", c.baseURL, albumID)
	var album Album
	if err := c.doJSON(ctx, http.MethodPatch, url, apiKey, payload, &album, "update album", http.StatusOK); err != nil {
		return Album{}, err
	}
	return album, nil
}

func (c *Client) DeleteAlbum(ctx context.Context, apiKey, albumID string) error {
	url := fmt.Sprintf("%s/albums/%s", c.baseURL, albumID)
	return c.doJSON(ctx, http.MethodDelete, url, apiKey, nil, nil, "delete album", http.StatusOK, http.StatusNoContent)
}

func (c *Client) AddAlbumAssets(ctx context.Context, apiKey, albumID string, assetIDs []string) ([]BulkAssetResult, error) {
	url := fmt.Sprintf("%s/albums/%s/assets", c.baseURL, albumID)
	var results []BulkAssetResult
	if err := c.doJSON(ctx, http.MethodPut, url, apiKey, bulkIDsPayload{IDs: assetIDs}, &results, "add album assets", http.StatusOK); err != nil {
		return nil, err
	}
	return results, nil
}

func (c *Client) RemoveAlbumAssets(ctx context.Context, apiKey, albumID string, assetIDs []string) ([]BulkAssetResult, error) {
	url := fmt.Sprintf("%s/albums/%s/assets", c.baseURL, albumID)
	var results []BulkAssetResult
	if err := c.doJSON(ctx, http.MethodDelete, url, apiKey, bulkIDsPayload{IDs: assetIDs}, &results, "remove album assets", http.StatusOK); err != nil {
		return nil, err
	}
	return results, nil
}

func (c *Client) doJSON(ctx context.Context, method, url, apiKey string, reqBody any, dest any, operation string, okStatuses ...int) error {
	var body io.Reader
	if reqBody != nil {
		data, err := json.Marshal(reqBody)
		if err != nil {
			return err
		}
		body = bytes.NewReader(data)
	}

	req, err := http.NewRequestWithContext(ctx, method, url, body)
	if err != nil {
		return err
	}
	req.Header.Set("x-api-key", apiKey)
	if reqBody != nil {
		req.Header.Set("Content-Type", "application/json")
	}

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

	for _, status := range okStatuses {
		if resp.StatusCode == status {
			if dest != nil {
				if err := json.NewDecoder(resp.Body).Decode(dest); err != nil {
					return err
				}
			}
			return nil
		}
	}

	io.Copy(io.Discard, resp.Body)
	return &HTTPStatusError{Operation: operation, StatusCode: resp.StatusCode}
}
