package parlament

import (
	"fmt"
	"strings"
)

// CurrentPeriod is the active legislative period code.
const CurrentPeriod = "XXVIII"

// DocumentType selects which legislative document category to query.
type DocumentType string

const (
	DocResolution        DocumentType = "BNR"  // Beschlüsse (resolutions)
	DocMotion            DocumentType = "ANTR" // Anträge (motions)
	DocGovBill           DocumentType = "RV"   // Regierungsvorlagen (government bills)
	DocCitizenInitiative DocumentType = "BI"   // Bürgerinitiativen (citizen initiatives)
	DocPetition          DocumentType = "PET"  // Petitionen (petitions)
	DocWrittenQuestion   DocumentType = "ANFR" // Schriftliche Anfragen (written questions)
	DocEUStatement       DocumentType = "EU"   // EU-Stellungnahmen (EU committee statements)
)

// Document is a legislative item (resolution, motion, or government bill).
type Document struct {
	Date        string
	Title       string // "Betreff" column
	Number      string // "Nummer" column, e.g. "69/BNR"
	GPCode      string
	Type        string // "Art" column
	Topics      string
	VoteResult   string // "Abstimmung 3. Lesung" — "1" when voted on
	VotesFor     string // "Dafür" — party names (JSON array)
	VotesAgainst string // "Dagegen" — party names (JSON array)
	VoteText     string // "Abstimmungstext" — compact summary e.g. "Dafür: V, S, N, G, Dagegen: F"
	// Path is the portal-relative URL, usable with Client.GetDetail.
	Path string // "HIS_URL" column e.g. "/gegenstand/XXVIII/I/14"
	// RouteID is the path segments after /gegenstand/, e.g. "XXVIII/I/14".
	RouteID string
}

type DocumentFilter struct {
	// Chamber is "NR" (Nationalrat) or "BR" (Bundesrat).
	Chamber []string
	// Period is the legislative period in Roman numerals, e.g. "XXVIII".
	Period []string
	// Type narrows to a specific document category (DocResolution, DocMotion, DocGovBill).
	Type DocumentType
	// Topics is a free-text topic filter.
	Topics []string
	// DateFrom in YYYY-MM-DD format.
	DateFrom string
}

// ListDocuments returns legislative documents matching the given filter.
func (c *Client) ListDocuments(f DocumentFilter) ([]Document, int, error) {
	payload := map[string]any{}
	if len(f.Chamber) > 0 {
		payload["NRBR"] = f.Chamber
	}
	if len(f.Period) > 0 {
		payload["GP_CODE"] = f.Period
	}
	if f.Type != "" {
		payload["VHG"] = []string{string(f.Type)}
	}
	if len(f.Topics) > 0 {
		payload["THEMEN"] = f.Topics
	}
	if f.DateFrom != "" {
		payload["DATUM_VON"] = []string{f.DateFrom}
	}

	resp, err := c.postDataset(101, payload)
	if err != nil {
		return nil, 0, err
	}

	idx := colIndex(resp.Header)
	docs := make([]Document, 0, len(resp.Rows))
	for _, row := range resp.Rows {
		docs = append(docs, Document{
			Date:         strCol(row, idx, "Datum"),
			Title:        strCol(row, idx, "Betreff"),
			Number:       strCol(row, idx, "Nummer"),
			GPCode:       strCol(row, idx, "GP_CODE"),
			Type:         strCol(row, idx, "Art"),
			Topics:       strCol(row, idx, "THEMEN"),
			VoteResult:   strCol(row, idx, "Abstimmung 3. Lesung"),
			VotesFor:     strCol(row, idx, "Dafür"),
			VotesAgainst: strCol(row, idx, "Dagegen"),
			VoteText: strCol(row, idx, "Abstimmungstext"),
			Path:     strCol(row, idx, "HIS_URL"),
			RouteID:  strings.TrimPrefix(strCol(row, idx, "HIS_URL"), "/gegenstand/"),
		})
	}
	return docs, resp.Count, nil
}

// VoteFaction holds one faction's vote in a plenary division.
type VoteFaction struct {
	Name    string
	Code    string
	Color   string
	Seats   int
	InFavor bool
}

// DocFile is a single downloadable file linked from a document.
type DocFile struct {
	Link string
	Type string // "PDF" or "HTML"
}

// DocGroup is a named group of files (e.g. "Gesetzestext", "Beschlussformel NR").
type DocGroup struct {
	Title string
	Files []DocFile
}

// DocumentVoteDetail holds the structured vote result for a single document.
type DocumentVoteDetail struct {
	Title        string
	Description  string
	Date         string
	DocType      string
	Zitation     string
	Passed       bool
	Summary      string // e.g. "Dafür: V, S, N, G, Dagegen: F"
	Comment      string
	Factions     []VoteFaction
	ForSeats     int
	AgainstSeats int
	ForPercent   int // 0–100, for the seat bar width
	DocGroups    []DocGroup
	Topics       []string
}

// GetDocumentVote fetches the vote detail for a document via its portal path
// (e.g. "/gegenstand/XXVIII/I/14"). Returns nil, nil when the document has
// no vote data.
func (c *Client) GetDocumentVote(path string) (*DocumentVoteDetail, error) {
	raw, err := c.GetDetail(path)
	if err != nil {
		return nil, err
	}

	// content may be a single object or an array; normalise to map.
	var content map[string]any
	switch v := raw["content"].(type) {
	case map[string]any:
		content = v
	case []any:
		if len(v) > 0 {
			content, _ = v[0].(map[string]any)
		}
	}
	if content == nil {
		return nil, nil
	}

	voteRaw, _ := content["vote"].(map[string]any)

	detail := &DocumentVoteDetail{
		Title:       strAny(content["title"]),
		Description: strAny(content["description"]),
		Date:        strAny(content["einlangen"]),
		DocType:     strAny(content["type"]),
		Zitation:    strAny(content["zitation"]),
	}
	if voteRaw != nil {
		detail.Passed = boolAny(voteRaw["infavor"])
		detail.Summary = strAny(voteRaw["text"])
		detail.Comment = strAny(voteRaw["comment"])
	}

	// Parse factions and accumulate seat totals.
	if results, ok := voteRaw["result"].([]any); ok {
		for _, r := range results {
			fr, _ := r.(map[string]any)
			if fr == nil {
				continue
			}
			seats := 0
			if n, ok := fr["fraction"].(float64); ok {
				seats = int(n)
			}
			f := VoteFaction{
				Name:    strAny(fr["text"]),
				Code:    strAny(fr["code"]),
				Color:   strAny(fr["color"]),
				Seats:   seats,
				InFavor: boolAny(fr["infavor"]),
			}
			detail.Factions = append(detail.Factions, f)
			if f.InFavor {
				detail.ForSeats += seats
			} else {
				detail.AgainstSeats += seats
			}
		}
	}
	if total := detail.ForSeats + detail.AgainstSeats; total > 0 {
		detail.ForPercent = detail.ForSeats * 100 / total
	}

	// Parse document groups.
	if docs, ok := content["documents"].([]any); ok {
		for _, dg := range docs {
			grp, _ := dg.(map[string]any)
			if grp == nil {
				continue
			}
			g := DocGroup{Title: strAny(grp["title"])}
			if files, ok := grp["documents"].([]any); ok {
				for _, f := range files {
					fm, _ := f.(map[string]any)
					if fm == nil {
						continue
					}
					g.Files = append(g.Files, DocFile{
						Link: strAny(fm["link"]),
						Type: strAny(fm["type"]),
					})
				}
			}
			detail.DocGroups = append(detail.DocGroups, g)
		}
	}

	// Parse topic bubbles.
	if topicsRaw, ok := content["topics"].(map[string]any); ok {
		if data, ok := topicsRaw["data"].(map[string]any); ok {
			if bubbles, ok := data["bubbles"].([]any); ok {
				for _, b := range bubbles {
					if bm, ok := b.(map[string]any); ok {
						detail.Topics = append(detail.Topics, strAny(bm["label"]))
					}
				}
			}
		}
	}

	return detail, nil
}

func strAny(v any) string {
	if v == nil {
		return ""
	}
	if s, ok := v.(string); ok {
		return s
	}
	return fmt.Sprintf("%v", v)
}

func boolAny(v any) bool {
	b, _ := v.(bool)
	return b
}

// Protocol is a stenographic protocol of a plenary session.
type Protocol struct {
	Date    string
	Session string
	Chamber string // "NBVS" column, e.g. "NRSITZ"
	// Path is the portal-relative URL, usable with Client.GetDetail.
	Path string // "uri" column
}

type ProtocolFilter struct {
	// Chamber is "NRSITZ" (Nationalrat) or "BRSITZ" (Bundesrat).
	Chamber []string
	// Period is the legislative period in Roman numerals, e.g. "XXVIII".
	Period []string
}

// ListProtocols returns stenographic protocols matching the given filter.
func (c *Client) ListProtocols(f ProtocolFilter) ([]Protocol, int, error) {
	payload := map[string]any{}
	if len(f.Chamber) > 0 {
		payload["NBVS"] = f.Chamber
	}
	if len(f.Period) > 0 {
		payload["GP_CODE"] = f.Period
	}

	resp, err := c.postDataset(211, payload)
	if err != nil {
		return nil, 0, err
	}

	idx := colIndex(resp.Header)
	protocols := make([]Protocol, 0, len(resp.Rows))
	for _, row := range resp.Rows {
		protocols = append(protocols, Protocol{
			Date:    strCol(row, idx, "Datum"),
			Session: strCol(row, idx, "Sitzung"),
			Chamber: strCol(row, idx, "NBVS"),
			Path:    strCol(row, idx, "uri"),
		})
	}
	return protocols, resp.Count, nil
}
