// Package kilo provides authentication and token management functionality
// for Kilo AI services.
package kilo

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

const (
	// BaseURL is the base URL for the Kilo AI API.
	BaseURL = "https://api.kilo.ai/api"
)

// DeviceAuthResponse represents the response from initiating device flow.
type DeviceAuthResponse struct {
	Code            string `json:"code"`
	VerificationURL string `json:"verificationUrl"`
	ExpiresIn       int    `json:"expiresIn"`
}

// DeviceStatusResponse represents the response when polling for device flow status.
type DeviceStatusResponse struct {
	Status    string `json:"status"`
	Token     string `json:"token"`
	UserEmail string `json:"userEmail"`
}

// Profile represents the user profile from Kilo AI.
type Profile struct {
	Email string         `json:"email"`
	Orgs  []Organization `json:"organizations"`
}

// Organization represents a Kilo AI organization.
type Organization struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

// Defaults represents default settings for an organization or user.
type Defaults struct {
	Model string `json:"model"`
}

// KiloAuth provides methods for handling the Kilo AI authentication flow.
type KiloAuth struct {
	client *http.Client
}

// NewKiloAuth creates a new instance of KiloAuth.
func NewKiloAuth() *KiloAuth {
	return &KiloAuth{
		client: &http.Client{Timeout: 30 * time.Second},
	}
}

// InitiateDeviceFlow starts the device authentication flow.
func (k *KiloAuth) InitiateDeviceFlow(ctx context.Context) (*DeviceAuthResponse, error) {
	resp, err := k.client.Post(BaseURL+"/device-auth/codes", "application/json", nil)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to initiate device flow: status %d", resp.StatusCode)
	}

	var data DeviceAuthResponse
	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
		return nil, err
	}
	return &data, nil
}

// PollForToken polls for the device flow completion.
func (k *KiloAuth) PollForToken(ctx context.Context, code string) (*DeviceStatusResponse, error) {
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return nil, ctx.Err()
		case <-ticker.C:
			resp, err := k.client.Get(BaseURL + "/device-auth/codes/" + code)
			if err != nil {
				return nil, err
			}
			defer resp.Body.Close()

			var data DeviceStatusResponse
			if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
				return nil, err
			}

			switch data.Status {
			case "approved":
				return &data, nil
			case "denied", "expired":
				return nil, fmt.Errorf("device flow %s", data.Status)
			case "pending":
				continue
			default:
				return nil, fmt.Errorf("unknown status: %s", data.Status)
			}
		}
	}
}

// GetProfile fetches the user's profile.
func (k *KiloAuth) GetProfile(ctx context.Context, token string) (*Profile, error) {
	req, _ := http.NewRequestWithContext(ctx, "GET", BaseURL+"/profile", nil)
	req.Header.Set("Authorization", "Bearer "+token)

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

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get profile: status %d", resp.StatusCode)
	}

	var profile Profile
	if err := json.NewDecoder(resp.Body).Decode(&profile); err != nil {
		return nil, err
	}
	return &profile, nil
}

// GetDefaults fetches default settings for an organization.
func (k *KiloAuth) GetDefaults(ctx context.Context, token, orgID string) (*Defaults, error) {
	url := BaseURL + "/defaults"
	if orgID != "" {
		url = BaseURL + "/organizations/" + orgID + "/defaults"
	}

	req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
	req.Header.Set("Authorization", "Bearer "+token)

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

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get defaults: status %d", resp.StatusCode)
	}

	var defaults Defaults
	if err := json.NewDecoder(resp.Body).Decode(&defaults); err != nil {
		return nil, err
	}
	return &defaults, nil
}
