package parlament

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

const baseURL = "https://www.parlament.gv.at"

// Client is a thin HTTP client for the Austrian parliament open-data API.
// No authentication is required; all endpoints are public.
type Client struct {
	http *http.Client
}

func NewClient() *Client {
	return &Client{
		http: &http.Client{Timeout: 15 * time.Second},
	}
}

// filterResponse is the raw shape returned by every list/filter endpoint.
type filterResponse struct {
	Pages  int       `json:"pages"`
	Count  int       `json:"count"`
	Header []colMeta `json:"header"`
	Rows   [][]any   `json:"rows"`
}

// colMeta only captures what we actually use; other fields vary by endpoint
// and have inconsistent types (e.g. sortable is "1"/"0" on newer endpoints).
type colMeta struct {
	Label  string `json:"label"`
	Hidden bool   `json:"hidden"`
}

// colIndex builds a label→position map so rows can be accessed by column name.
func colIndex(headers []colMeta) map[string]int {
	m := make(map[string]int, len(headers))
	for i, h := range headers {
		m[h.Label] = i
	}
	return m
}

func anyCol(row []any, idx map[string]int, label string) any {
	i, ok := idx[label]
	if !ok || i >= len(row) {
		return nil
	}
	return row[i]
}

func strCol(row []any, idx map[string]int, label string) string {
	i, ok := idx[label]
	if !ok || i >= len(row) || row[i] == nil {
		return ""
	}
	if s, ok := row[i].(string); ok {
		return s
	}
	return fmt.Sprintf("%v", row[i])
}

// postFilter calls the legacy FBEZ-style filter endpoint.
func (c *Client) postFilter(fbez string, payload any) (*filterResponse, error) {
	url := fmt.Sprintf("%s/Filter/api/json/post?jsMode=EVAL&FBEZ=%s&listeId=undefined&showAll=true", baseURL, fbez)
	return c.post(url, payload)
}

// postDataset calls the newer numeric-dataset-ID filter endpoint.
func (c *Client) postDataset(datasetID int, payload any) (*filterResponse, error) {
	url := fmt.Sprintf("%s/Filter/api/filter/data/%d?js=eval&showAll=true", baseURL, datasetID)
	return c.post(url, payload)
}

func (c *Client) post(url string, payload any) (*filterResponse, error) {
	body, err := json.Marshal(payload)
	if err != nil {
		return nil, err
	}

	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Referer", baseURL+"/")
	req.Header.Set("X-Requested-With", "XMLHttpRequest")
	req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; research-bot)")

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

	raw, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var result filterResponse
	if err := json.Unmarshal(raw, &result); err != nil {
		return nil, fmt.Errorf("unexpected response (not JSON): %s", truncate(string(raw), 200))
	}
	return &result, nil
}

// GetDetail fetches a single record by appending ?json=TRUE to any detail page path.
// path example: "/gegenstand/NR/I/I_00458"
func (c *Client) GetDetail(path string) (map[string]any, error) {
	url := baseURL + path + "?json=TRUE"
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Referer", baseURL+"/")
	req.Header.Set("X-Requested-With", "XMLHttpRequest")
	req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; research-bot)")

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

	var result map[string]any
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return nil, err
	}
	return result, nil
}

func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n] + "..."
}
