package download

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

type SABnzbdClient struct {
	baseURL string
	apiKey  string
	client  *http.Client
}

func NewSABnzbdClient(baseURL, apiKey string) *SABnzbdClient {
	return &SABnzbdClient{
		baseURL: strings.TrimRight(baseURL, "/"),
		apiKey:  apiKey,
		client:  &http.Client{Timeout: 30 * time.Second},
	}
}

func (s *SABnzbdClient) Add(ctx context.Context, url string, category string) (string, error) {
	if category == "" {
		category = "umm"
	}
	apiURL := fmt.Sprintf("%s/api?mode=addurl&output=json&apikey=%s&name=%s&nzbname=umm&cat=%s",
		s.baseURL, s.apiKey, url, category)

	resp, err := s.doRequest(ctx, apiURL)
	if err != nil {
		return "", fmt.Errorf("sabnzbd add: %w", err)
	}
	defer resp.Body.Close()

	var result sabAddResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return "", fmt.Errorf("sabnzbd add decode: %w", err)
	}

	if !result.Status {
		return "", fmt.Errorf("sabnzbd add failed: %s", result.Error)
	}

	if len(result.NzoIDs) == 0 {
		return "", fmt.Errorf("sabnzbd add: no nzo_id returned")
	}

	return result.NzoIDs[0], nil
}

func (s *SABnzbdClient) GetProgress(ctx context.Context, id string) (*DownloadProgress, error) {
	apiURL := fmt.Sprintf("%s/api?mode=queue&output=json&apikey=%s", s.baseURL, s.apiKey)

	resp, err := s.doRequest(ctx, apiURL)
	if err != nil {
		return nil, fmt.Errorf("sabnzbd queue: %w", err)
	}
	defer resp.Body.Close()

	var result sabQueueResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("sabnzbd queue decode: %w", err)
	}

	for _, slot := range result.Queue.Slots {
		if slot.NzoID == id {
			sizeMB, _ := strconv.ParseFloat(slot.SizeTotal, 64)
			downloadedMB, _ := strconv.ParseFloat(slot.MB, 64)
			size := int64(sizeMB * 1024 * 1024)

			progress := float64(0)
			if sizeMB > 0 {
				progress = (downloadedMB / sizeMB) * 100
			}

			eta := parseTimeLeft(slot.TimeLeft)

			return &DownloadProgress{
				ID:       slot.NzoID,
				Name:     slot.Filename,
				Status:   slot.Status,
				Progress: progress,
				Speed:    int64(slot.Speed * 1024),
				ETA:      eta,
				Size:     size,
			}, nil
		}
	}

	return nil, fmt.Errorf("sabnzbd: item %s not found in queue", id)
}

func (s *SABnzbdClient) GetCompleted(ctx context.Context) ([]CompletedDownload, error) {
	apiURL := fmt.Sprintf("%s/api?mode=history&output=json&apikey=%s", s.baseURL, s.apiKey)

	resp, err := s.doRequest(ctx, apiURL)
	if err != nil {
		return nil, fmt.Errorf("sabnzbd history: %w", err)
	}
	defer resp.Body.Close()

	var result sabHistoryResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, fmt.Errorf("sabnzbd history decode: %w", err)
	}

	var completed []CompletedDownload
	for _, slot := range result.History.Slots {
		if strings.EqualFold(slot.Status, "Completed") {
			size, _ := strconv.ParseInt(slot.Size, 10, 64)
			completed = append(completed, CompletedDownload{
				ID:         slot.NzoID,
				Name:       slot.Name,
				OutputPath: slot.Storage,
				Size:       size,
				Status:     slot.Status,
			})
		}
	}

	return completed, nil
}

func (s *SABnzbdClient) Remove(ctx context.Context, id string) error {
	apiURL := fmt.Sprintf("%s/api?mode=queue&output=json&apikey=%s&name=delete&value=%s&del_files=1",
		s.baseURL, s.apiKey, id)

	resp, err := s.doRequest(ctx, apiURL)
	if err != nil {
		return fmt.Errorf("sabnzbd remove: %w", err)
	}
	resp.Body.Close()

	return nil
}

func (s *SABnzbdClient) Pause(ctx context.Context, id string) error {
	apiURL := fmt.Sprintf("%s/api?mode=queue&output=json&apikey=%s&name=pause&id=%s",
		s.baseURL, s.apiKey, id)

	resp, err := s.doRequest(ctx, apiURL)
	if err != nil {
		return fmt.Errorf("sabnzbd pause: %w", err)
	}
	resp.Body.Close()

	return nil
}

func (s *SABnzbdClient) Resume(ctx context.Context, id string) error {
	apiURL := fmt.Sprintf("%s/api?mode=queue&output=json&apikey=%s&name=resume&id=%s",
		s.baseURL, s.apiKey, id)

	resp, err := s.doRequest(ctx, apiURL)
	if err != nil {
		return fmt.Errorf("sabnzbd resume: %w", err)
	}
	resp.Body.Close()

	return nil
}

func (s *SABnzbdClient) doRequest(ctx context.Context, url string) (*http.Response, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	return s.client.Do(req)
}

func parseTimeLeft(timeLeft string) int {
	if timeLeft == "" {
		return 0
	}
	parts := strings.Split(timeLeft, ":")
	if len(parts) != 3 {
		return 0
	}
	hours, _ := strconv.Atoi(parts[0])
	minutes, _ := strconv.Atoi(parts[1])
	seconds, _ := strconv.Atoi(parts[2])
	return hours*3600 + minutes*60 + seconds
}

type sabAddResponse struct {
	Status bool     `json:"status"`
	NzoIDs []string `json:"nzo_ids"`
	Error  string   `json:"error,omitempty"`
}

type sabQueueResponse struct {
	Queue struct {
		Slots []sabQueueSlot `json:"slots"`
	} `json:"queue"`
}

type sabQueueSlot struct {
	NzoID      string  `json:"nzo_id"`
	Filename   string  `json:"filename"`
	Status     string  `json:"status"`
	MB         string  `json:"mb"`
	SizeTotal  string  `json:"sizeleft"`
	TimeLeft   string  `json:"timeleft"`
	Speed      float64 `json:"speed"`
	Percentage string `json:"percentage"`
}

type sabHistoryResponse struct {
	History struct {
		Slots []sabHistorySlot `json:"slots"`
	} `json:"history"`
}

type sabHistorySlot struct {
	NzoID   string `json:"nzo_id"`
	Name    string `json:"name"`
	Status  string `json:"status"`
	Storage string `json:"storage"`
	Size    string `json:"size"`
}

func init() {
	_ = io.EOF
}
