// SPDX-License-Identifier: GPL-3.0-or-later

// Package client provides SSE (Server-Sent Events) support for progress streaming.
package client

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

// Event represents a server-sent event.
type Event struct {
	ID    string
	Event string
	Data  string
}

// SSEClient handles Server-Sent Events streaming.
type SSEClient struct {
	client  *Client
	onEvent func(Event)
	onError func(error)
}

// NewSSEClient creates a new SSE client.
func NewSSEClient(c *Client) *SSEClient {
	return &SSEClient{
		client: c,
	}
}

// OnEvent sets the event handler callback.
func (s *SSEClient) OnEvent(handler func(Event)) *SSEClient {
	s.onEvent = handler
	return s
}

// OnError sets the error handler callback.
func (s *SSEClient) OnError(handler func(error)) *SSEClient {
	s.onError = handler
	return s
}

// Stream connects to an SSE endpoint and streams events.
// This method blocks until the context is cancelled or the stream ends.
func (s *SSEClient) Stream(ctx context.Context, path string) error {
	url := s.client.baseURL + path

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Accept", "text/event-stream")
	req.Header.Set("Cache-Control", "no-cache")
	req.Header.Set("Connection", "keep-alive")

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("stream request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("stream failed: HTTP %d: %s", resp.StatusCode, string(body))
	}

	return s.readEvents(ctx, resp.Body)
}

// readEvents reads and parses SSE events from the response body.
func (s *SSEClient) readEvents(ctx context.Context, body io.Reader) error {
	reader := bufio.NewReader(body)
	var event Event

	for {
		select {
		case <-ctx.Done():
			return ctx.Err()
		default:
		}

		line, err := reader.ReadString('\n')
		if err != nil {
			if err == io.EOF {
				return nil
			}
			if s.onError != nil {
				s.onError(err)
			}
			return err
		}

		line = strings.TrimSuffix(line, "\n")
		line = strings.TrimSuffix(line, "\r")

		// Empty line signals end of event
		if line == "" {
			if event.Data != "" || event.Event != "" {
				if s.onEvent != nil {
					s.onEvent(event)
				}
				event = Event{}
			}
			continue
		}

		// Parse field
		if strings.HasPrefix(line, "id:") {
			event.ID = strings.TrimSpace(line[3:])
		} else if strings.HasPrefix(line, "event:") {
			event.Event = strings.TrimSpace(line[6:])
		} else if strings.HasPrefix(line, "data:") {
			data := strings.TrimSpace(line[5:])
			if event.Data != "" {
				event.Data += "\n"
			}
			event.Data += data
		} else if strings.HasPrefix(line, ":") {
			// Comment, ignore
			continue
		}
	}
}

// ProgressEvent represents a backup/restore progress event.
type ProgressEvent struct {
	JobID          string  `json:"job_id"`
	Status         string  `json:"status"`
	Progress       int     `json:"progress"`
	BytesProcessed int64   `json:"bytes_processed"`
	FilesProcessed int64   `json:"files_processed"`
	CurrentFile    string  `json:"current_file,omitempty"`
	Rate           float64 `json:"rate,omitempty"`
	ETA            int64   `json:"eta_ms,omitempty"`
	Error          string  `json:"error,omitempty"`
}

// ProgressHandler handles progress events.
type ProgressHandler struct {
	sseClient  *SSEClient
	onProgress func(ProgressEvent)
	onComplete func(ProgressEvent)
	onError    func(error)
}

// NewProgressHandler creates a new progress handler.
func NewProgressHandler(c *Client) *ProgressHandler {
	return &ProgressHandler{
		sseClient: NewSSEClient(c),
	}
}

// OnProgress sets the progress callback.
func (p *ProgressHandler) OnProgress(handler func(ProgressEvent)) *ProgressHandler {
	p.onProgress = handler
	return p
}

// OnComplete sets the completion callback.
func (p *ProgressHandler) OnComplete(handler func(ProgressEvent)) *ProgressHandler {
	p.onComplete = handler
	return p
}

// OnError sets the error callback.
func (p *ProgressHandler) OnError(handler func(error)) *ProgressHandler {
	p.onError = handler
	return p
}

// StreamBackupProgress streams backup progress events.
func (p *ProgressHandler) StreamBackupProgress(ctx context.Context) error {
	return p.streamProgress(ctx, "/api/v1/backup/progress")
}

// StreamRestoreProgress streams restore progress events.
func (p *ProgressHandler) StreamRestoreProgress(ctx context.Context) error {
	return p.streamProgress(ctx, "/api/v1/restore/progress")
}

// streamProgress streams progress events from the given path.
func (p *ProgressHandler) streamProgress(ctx context.Context, path string) error {
	p.sseClient.OnEvent(func(event Event) {
		// Parse progress event
		var progress ProgressEvent
		if err := parseJSON(event.Data, &progress); err != nil {
			if p.onError != nil {
				p.onError(err)
			}
			return
		}

		// Dispatch to appropriate handler
		switch event.Event {
		case "progress":
			if p.onProgress != nil {
				p.onProgress(progress)
			}
		case "complete":
			if p.onComplete != nil {
				p.onComplete(progress)
			}
		case "error":
			if p.onError != nil && progress.Error != "" {
				p.onError(fmt.Errorf("%s", progress.Error))
			}
		default:
			// Unknown event type, treat as progress
			if p.onProgress != nil {
				p.onProgress(progress)
			}
		}
	})

	p.sseClient.OnError(func(err error) {
		if p.onError != nil {
			p.onError(err)
		}
	})

	return p.sseClient.Stream(ctx, path)
}

// parseJSON parses JSON data.
func parseJSON(data string, v interface{}) error {
	return json.Unmarshal([]byte(data), v)
}
