package memory

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

// Client memory service client with JWT auth
type Client struct {
	baseURL    string
	httpClient *http.Client
	token      string
}

// NewClient creates memory service client
func NewClient(baseURL, token string) *Client {
	return &Client{
		baseURL: baseURL,
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
		token: token,
	}
}

// IngestRequest ingest knowledge record
type IngestRequest struct {
	Project  string                 `json:"project"`
	Source   string                 `json:"source"`
	Kind     string                 `json:"kind"` // L1|L2|reference
	Text     string                 `json:"text"`
	Metadata map[string]interface{} `json:"metadata,omitempty"`
}

// IngestResponse ingest response
type IngestResponse struct {
	ID            string `json:"id"`
	SHA256        string `json:"sha256"`
	QueueStatus   string `json:"queue_status"`
	IdempotencyID string `json:"idempotency_key"`
}

// Ingest creates knowledge record
func (c *Client) Ingest(ctx context.Context, req *IngestRequest) (*IngestResponse, error) {
	body, err := json.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("marshal ingest request: %w", err)
	}

	httpReq, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+"/memory/ingest", bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}

	c.setAuthHeader(httpReq)
	httpReq.Header.Set("Content-Type", "application/json")

	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return nil, fmt.Errorf("ingest request failed: %w", err)
	}
	defer resp.Body.Close()

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

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

	return &result, nil
}

// QueryRequest query memory
type QueryRequest struct {
	Project    string   `json:"project"`
	Query      string   `json:"query"`
	LevelFilter []string `json:"level_filter,omitempty"` // L1, L2, R
	Floor      float32  `json:"floor,omitempty"`
	Limit      int      `json:"limit,omitempty"`
	Scope      string   `json:"scope,omitempty"` // learned|reference|all
}

// QueryResult single search result
type QueryResult struct {
	ID            string  `json:"id"`
	Level         string  `json:"level"`
	Score         float32 `json:"score"`
	SemanticScore float32 `json:"semantic_score"`
	LexicalScore  float32 `json:"lexical_score"`
	Text          string  `json:"text"`
	Breadcrumb    string  `json:"breadcrumb"`
	Source        string  `json:"source"`
}

// QueryResponse query response
type QueryResponse struct {
	Query       string         `json:"query"`
	Results     []QueryResult  `json:"results"`
	TotalHits   int            `json:"total_hits"`
	SearchTimeMS int           `json:"search_time_ms"`
}

// Query searches knowledge
func (c *Client) Query(ctx context.Context, req *QueryRequest) (*QueryResponse, error) {
	if req.Limit == 0 {
		req.Limit = 10
	}

	body, err := json.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("marshal query request: %w", err)
	}

	httpReq, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+"/memory/query", bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}

	c.setAuthHeader(httpReq)
	httpReq.Header.Set("Content-Type", "application/json")

	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return nil, fmt.Errorf("query request failed: %w", err)
	}
	defer resp.Body.Close()

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

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

	return &result, nil
}

// ContextRequest retrieve context (three-tier)
type ContextRequest struct {
	Project         string `json:"project"`
	Tool            string `json:"tool"`
	Task            string `json:"task"`
	SignatureSource string `json:"signature_source"`
	Scope           string `json:"scope,omitempty"` // tool_context
	Budget          int    `json:"budget,omitempty"`
}

// ContextLesson lesson from context
type ContextLesson struct {
	Tier        int    `json:"tier"`
	Level       string `json:"level"`
	Score       float32 `json:"score"`
	Text        string `json:"text"`
	MatchedKind string `json:"matched_kind,omitempty"`
	SeenCount   int    `json:"seen_count,omitempty"`
	LastSeen    string `json:"last_seen,omitempty"`
}

// ContextSkill skill suggestion
type ContextSkill struct {
	Name string `json:"name"`
	Why  string `json:"why"`
}

// ContextBudget budget tracking
type ContextBudget struct {
	Requested   int    `json:"requested"`
	Used        int    `json:"used"`
	Dropped     int    `json:"dropped"`
	Degradation *string `json:"degradation"`
}

// ContextResponse context response
type ContextResponse struct {
	Tier     int              `json:"tier"`
	Lessons  []ContextLesson  `json:"lessons"`
	Skills   []ContextSkill   `json:"skills"`
	Budget   ContextBudget    `json:"budget"`
}

// Context retrieves context (three-tier retrieval)
func (c *Client) Context(ctx context.Context, req *ContextRequest) (*ContextResponse, error) {
	if req.Budget == 0 {
		req.Budget = 8192
	}

	body, err := json.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("marshal context request: %w", err)
	}

	httpReq, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+"/memory/context", bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}

	c.setAuthHeader(httpReq)
	httpReq.Header.Set("Content-Type", "application/json")

	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return nil, fmt.Errorf("context request failed: %w", err)
	}
	defer resp.Body.Close()

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

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

	return &result, nil
}

// VaultFile file in vault
type VaultFile struct {
	Path        string `json:"path"`
	Title       string `json:"title"`
	Level       string `json:"level"`
	UpdatedAt   string `json:"updated_at"`
	RecordCount int    `json:"record_count"`
}

// VaultResponse vault browse response
type VaultResponse struct {
	Project       string      `json:"project"`
	Files         []VaultFile `json:"files"`
	TotalRecords  int         `json:"total_records"`
}

// Vault browses vault files
func (c *Client) Vault(ctx context.Context, project string) (*VaultResponse, error) {
	httpReq, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/memory/vault?project=%s", c.baseURL, project), nil)
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}

	c.setAuthHeader(httpReq)

	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return nil, fmt.Errorf("vault request failed: %w", err)
	}
	defer resp.Body.Close()

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

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

	return &result, nil
}

// setAuthHeader sets JWT Bearer token
func (c *Client) setAuthHeader(req *http.Request) {
	if c.token != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))
	}
}

// Health checks memory service
func (c *Client) Health(ctx context.Context) (bool, error) {
	httpReq, err := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/health", nil)
	if err != nil {
		return false, err
	}

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

	return resp.StatusCode == http.StatusOK, nil
}
