package api

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"time"
	"woke/db"
)

// Client implements db.AlarmStore via HTTP calls to a remote woke server.
type Client struct {
	baseURL  string
	password string
	http     *http.Client
}

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

func (c *Client) do(method, path string, body any) (*http.Response, error) {
	var reqBody *bytes.Buffer
	if body != nil {
		data, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		reqBody = bytes.NewBuffer(data)
	} else {
		reqBody = &bytes.Buffer{}
	}

	req, err := http.NewRequest(method, c.baseURL+path, reqBody)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	if c.password != "" {
		req.Header.Set("Authorization", "Bearer "+c.password)
	}
	return c.http.Do(req)
}

func (c *Client) ListAlarms() ([]db.Alarm, error) {
	resp, err := c.do("GET", "/api/alarms", nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("server returned %d", resp.StatusCode)
	}

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

	result := make([]db.Alarm, len(alarms))
	for i, a := range alarms {
		result[i] = a.toAlarm()
	}
	return result, nil
}

func (c *Client) GetAlarm(id int) (db.Alarm, error) {
	resp, err := c.do("GET", fmt.Sprintf("/api/alarms/%d", id), nil)
	if err != nil {
		return db.Alarm{}, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return db.Alarm{}, fmt.Errorf("server returned %d", resp.StatusCode)
	}

	var a alarmResponse
	if err := json.NewDecoder(resp.Body).Decode(&a); err != nil {
		return db.Alarm{}, err
	}
	return a.toAlarm(), nil
}

func (c *Client) CreateAlarm(a db.Alarm) (int, error) {
	req := alarmRequest{
		Name:        a.Name,
		Description: a.Description,
		Time:        a.Time,
		Trigger:     a.Trigger,
		SoundPath:   a.SoundPath,
		Enabled:     &a.Enabled,
	}
	resp, err := c.do("POST", "/api/alarms", req)
	if err != nil {
		return 0, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated {
		return 0, fmt.Errorf("server returned %d", resp.StatusCode)
	}

	var created alarmResponse
	if err := json.NewDecoder(resp.Body).Decode(&created); err != nil {
		return 0, err
	}
	return created.ID, nil
}

func (c *Client) UpdateAlarm(a db.Alarm) error {
	req := alarmRequest{
		Name:        a.Name,
		Description: a.Description,
		Time:        a.Time,
		Trigger:     a.Trigger,
		SoundPath:   a.SoundPath,
		Enabled:     &a.Enabled,
	}
	resp, err := c.do("PUT", fmt.Sprintf("/api/alarms/%d", a.ID), req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("server returned %d", resp.StatusCode)
	}
	return nil
}

func (c *Client) DeleteAlarm(id int) error {
	resp, err := c.do("DELETE", fmt.Sprintf("/api/alarms/%d", id), nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusNoContent {
		return fmt.Errorf("server returned %d", resp.StatusCode)
	}
	return nil
}

func (c *Client) ToggleAlarm(id int) error {
	resp, err := c.do("PATCH", fmt.Sprintf("/api/alarms/%d/toggle", id), nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("server returned %d", resp.StatusCode)
	}
	return nil
}

func (c *Client) MarkTriggered(id int) error {
	// Client mode doesn't mark triggered — server handles this
	return nil
}

func (a alarmResponse) toAlarm() db.Alarm {
	alarm := db.Alarm{
		ID:          a.ID,
		Name:        a.Name,
		Description: a.Description,
		Time:        a.Time,
		Trigger:     a.Trigger,
		SoundPath:   a.SoundPath,
		Enabled:     a.Enabled,
		SnoozeCount: a.SnoozeCount,
	}
	if t, err := time.Parse("2006-01-02T15:04:05Z", a.CreatedAt); err == nil {
		alarm.CreatedAt = t
	}
	if t, err := time.Parse("2006-01-02T15:04:05Z", a.UpdatedAt); err == nil {
		alarm.UpdatedAt = t
	}
	if a.LastTriggered != nil {
		if t, err := time.Parse("2006-01-02T15:04:05Z", *a.LastTriggered); err == nil {
			alarm.LastTriggered = &t
		}
	}
	return alarm
}
