package calc

import (
	"fmt"
	"math"
)

// Constants for attorney fee calculation.
const (
	ErhoehungsFaktor    = 0.3
	ErhoehungsFaktorMax = 2.0
	Auslagenpauschale   = 20.0
)

// AttorneyBreakdown shows the detailed fee breakdown for one attorney.
type AttorneyBreakdown struct {
	BaseFee            float64 `json:"baseFee"`
	Verfahrensgebuehr  float64 `json:"verfahrensgebuehr"`
	Erhoehungsgebuehr  float64 `json:"erhoehungsgebuehr"`
	Terminsgebuehr     float64 `json:"terminsgebuehr"`
	Auslagenpauschale  float64 `json:"pauschale"`
	NettoTotal         float64 `json:"nettoTotal"`
	MwSt               float64 `json:"mwst"`
	BruttoPerAttorney  float64 `json:"bruttoPerAttorney"`
	Count              int     `json:"count"`
	BruttoTotal        float64 `json:"bruttoTotal"`
}

// DEInstanceResult is the full cost breakdown for one DE court instance.
type DEInstanceResult struct {
	Instance             string             `json:"instance"`
	Label                string             `json:"label"`
	LabelEN              string             `json:"labelEN"`
	Enabled              bool               `json:"enabled"`
	CourtFee             float64            `json:"courtFee"`
	CourtFeeBase         float64            `json:"courtFeeBase"`
	CourtFeeFactor       float64            `json:"courtFeeFactor"`
	CourtFeeBasis        string             `json:"courtFeeBasis"`
	Attorney             *AttorneyBreakdown `json:"attorney,omitempty"`
	PatentAttorney       *AttorneyBreakdown `json:"patentAttorney,omitempty"`
	InstanceTotal        float64            `json:"instanceTotal"`
}

// UPCInstanceResult is the cost breakdown for one UPC instance.
type UPCInstanceResult struct {
	Instance              string  `json:"instance"`
	Label                 string  `json:"label"`
	LabelEN               string  `json:"labelEN"`
	Enabled               bool    `json:"enabled"`
	FixedFee              float64 `json:"fixedFee"`
	ValueBasedFee         float64 `json:"valueBasedFee"`
	CourtFeesTotal        float64 `json:"courtFeesTotal"`
	CourtFeesSME          float64 `json:"courtFeesSME"`
	RecoverableCeiling    float64 `json:"recoverableCostsCeiling"`
	InstanceTotal         float64 `json:"instanceTotal"`
}

// EPAInstanceResult is the cost breakdown for an EPA proceeding.
type EPAInstanceResult struct {
	Instance      string  `json:"instance"`
	Label         string  `json:"label"`
	LabelEN       string  `json:"labelEN"`
	Enabled       bool    `json:"enabled"`
	Fee           float64 `json:"fee"`
	IsSME         bool    `json:"isSME"`
	InstanceTotal float64 `json:"instanceTotal"`
}

// InstanceInput describes one instance's configuration from the client.
type InstanceInput struct {
	Enabled            bool    `json:"enabled"`
	FeeVersion         string  `json:"feeVersion,omitempty"`
	NumAttorneys       int     `json:"numAttorneys,omitempty"`
	NumPatentAttorneys int     `json:"numPatentAttorneys,omitempty"`
	NumClients         int     `json:"numClients,omitempty"`
	OralHearing        bool    `json:"oralHearing,omitempty"`
	IsSME              bool    `json:"isSME,omitempty"`
	IncludeRevocation  bool    `json:"includeRevocation,omitempty"`
}

// CostRequest is the JSON request body for the Kostenrechner API.
type CostRequest struct {
	Streitwert float64                  `json:"streitwert"`
	VATRate    float64                  `json:"vatRate"`
	Instances  map[string]InstanceInput `json:"instances"`
}

// CostResponse is the JSON response for the Kostenrechner API.
type CostResponse struct {
	DEResults  []DEInstanceResult  `json:"deResults"`
	UPCResults []UPCInstanceResult `json:"upcResults"`
	EPAResults []EPAInstanceResult `json:"epaResults"`
	Totals     CostTotals          `json:"totals"`
}

// CostTotals summarizes the total costs across all instances.
type CostTotals struct {
	CourtFees          float64 `json:"courtFees"`
	AttorneyFees       float64 `json:"attorneyFees"`
	PatentAttorneyFees float64 `json:"patentAttorneyFees"`
	EPAFees            float64 `json:"epaFees"`
	GrandTotal         float64 `json:"grandTotal"`
}

// resolveFeeVersion resolves aliases and returns the bracket list.
func resolveFeeVersion(version string) ([]FeeBracket, error) {
	if alias, ok := FeeScheduleAliases[version]; ok {
		version = alias
	}
	schedule, ok := FeeSchedules[version]
	if !ok {
		return nil, fmt.Errorf("unknown fee version: %s", version)
	}
	return schedule.Brackets, nil
}

// GermanFeeStreitwertCap is the §34 GKG / §22(2) RVG hard ceiling: above
// 30M EUR Streitwert, German court and attorney fees stay at the 30M-row
// value. UPC value-based fees use a separate code path (lookupUPCValueFee)
// and are not affected by this cap.
const GermanFeeStreitwertCap = 30_000_000.0

// ComputeBaseFee calculates the 1.0x base fee using the step-based accumulator.
// isRVG=true for attorney fees (RVG), false for court fees (GKG).
func ComputeBaseFee(streitwert float64, isRVG bool, version string) (float64, error) {
	brackets, err := resolveFeeVersion(version)
	if err != nil {
		return 0, err
	}

	if streitwert > GermanFeeStreitwertCap {
		streitwert = GermanFeeStreitwertCap
	}

	remaining := streitwert
	fee := 0.0
	lowerBound := 0.0

	for _, b := range brackets {
		increment := b.GKGIncrement
		if isRVG {
			increment = b.RVGIncrement
		}

		bracketSize := b.UpperBound - lowerBound
		if math.IsInf(b.UpperBound, 1) {
			bracketSize = remaining
		}
		portion := math.Min(remaining, bracketSize)
		if portion <= 0 {
			break
		}

		if lowerBound == 0 {
			// First bracket: minimum fee = one increment
			fee += increment
			stepsAfterFirst := math.Max(0, math.Ceil((portion-b.StepSize)/b.StepSize))
			fee += stepsAfterFirst * increment
		} else {
			steps := math.Ceil(portion / b.StepSize)
			fee += steps * increment
		}

		remaining -= portion
		lowerBound = b.UpperBound
		if remaining <= 0 {
			break
		}
	}
	return fee, nil
}

// computeAttorneyFees calculates fees for a single attorney.
func computeAttorneyFees(streitwert float64, version string, vgFactor, tgFactor float64, oralHearing bool, numClients, count int, vatRate float64) (*AttorneyBreakdown, error) {
	if count <= 0 {
		return nil, nil
	}

	baseFee, err := ComputeBaseFee(streitwert, true, version)
	if err != nil {
		return nil, err
	}

	vg := vgFactor * baseFee
	erhoehung := 0.0
	if numClients > 1 {
		factor := math.Min(float64(numClients-1)*ErhoehungsFaktor, ErhoehungsFaktorMax)
		erhoehung = factor * baseFee
	}
	tg := 0.0
	if oralHearing {
		tg = tgFactor * baseFee
	}

	netto := vg + erhoehung + tg + Auslagenpauschale
	mwst := netto * vatRate
	brutto := netto + mwst

	return &AttorneyBreakdown{
		BaseFee:           round2(baseFee),
		Verfahrensgebuehr: round2(vg),
		Erhoehungsgebuehr: round2(erhoehung),
		Terminsgebuehr:    round2(tg),
		Auslagenpauschale: Auslagenpauschale,
		NettoTotal:        round2(netto),
		MwSt:              round2(mwst),
		BruttoPerAttorney: round2(brutto),
		Count:             count,
		BruttoTotal:       round2(brutto * float64(count)),
	}, nil
}

// ComputeDEInstance calculates all costs for one DE court instance.
func ComputeDEInstance(streitwert float64, input InstanceInput, meta InstanceMeta, vatRate float64) (*DEInstanceResult, error) {
	if !input.Enabled {
		return &DEInstanceResult{
			Instance: meta.Key,
			Label:    meta.Label,
			LabelEN:  meta.LabelEN,
			Enabled:  false,
		}, nil
	}

	version := input.FeeVersion
	if version == "" {
		version = "Aktuell"
	}

	// Court fees: factor × base fee (GKG or PatKostG)
	baseFee, err := ComputeBaseFee(streitwert, false, version)
	if err != nil {
		return nil, err
	}
	courtFee := round2(meta.CourtFeeFactor * baseFee)

	// Attorney fees
	ra, err := computeAttorneyFees(streitwert, version, meta.RAVGFactor, meta.RATGFactor, input.OralHearing, input.NumClients, input.NumAttorneys, vatRate)
	if err != nil {
		return nil, err
	}

	// Patent attorney fees
	var pa *AttorneyBreakdown
	if meta.HasPatentAttorneys {
		pa, err = computeAttorneyFees(streitwert, version, meta.PAVGFactor, meta.PATGFactor, input.OralHearing, input.NumClients, input.NumPatentAttorneys, vatRate)
		if err != nil {
			return nil, err
		}
	}

	raTotal := 0.0
	if ra != nil {
		raTotal = ra.BruttoTotal
	}
	paTotal := 0.0
	if pa != nil {
		paTotal = pa.BruttoTotal
	}

	return &DEInstanceResult{
		Instance:       meta.Key,
		Label:          meta.Label,
		LabelEN:        meta.LabelEN,
		Enabled:        true,
		CourtFee:       courtFee,
		CourtFeeBase:   round2(baseFee),
		CourtFeeFactor: meta.CourtFeeFactor,
		CourtFeeBasis:  meta.FeeBasis,
		Attorney:       ra,
		PatentAttorney: pa,
		InstanceTotal:  round2(courtFee + raTotal + paTotal),
	}, nil
}

// lookupUPCValueFee finds the value-based fee for a given Streitwert.
func lookupUPCValueFee(streitwert float64, brackets []UPCFeeBracket) float64 {
	for _, b := range brackets {
		if b.MaxValue == nil || streitwert <= *b.MaxValue {
			return b.Fee
		}
	}
	return brackets[len(brackets)-1].Fee
}

// lookupRecoverableCosts finds the recoverable cost ceiling for a given Streitwert.
func lookupRecoverableCosts(streitwert float64, table []UPCRecoverableCost) float64 {
	for _, e := range table {
		if e.MaxValue == nil || streitwert <= *e.MaxValue {
			return e.Ceiling
		}
	}
	return table[len(table)-1].Ceiling
}

// ComputeUPCInstance calculates UPC costs for one instance.
func ComputeUPCInstance(streitwert float64, input InstanceInput, instanceKey string) (*UPCInstanceResult, error) {
	label := "UPC (1. Instanz)"
	labelEN := "UPC (First Instance)"
	if instanceKey == "UPC_APPEAL" {
		label = "UPC (Berufung)"
		labelEN = "UPC (Appeal)"
	}

	if !input.Enabled {
		return &UPCInstanceResult{
			Instance: instanceKey,
			Label:    label,
			LabelEN:  labelEN,
			Enabled:  false,
		}, nil
	}

	version := input.FeeVersion
	if version == "" {
		version = "2026"
	}

	feeData, ok := UPCFeeSchedules[version]
	if !ok {
		return nil, fmt.Errorf("unknown UPC fee version: %s", version)
	}

	fixedFee := feeData.FixedInfringement
	if input.IncludeRevocation {
		fixedFee += feeData.FixedRevocation
	}

	valueBasedFee := lookupUPCValueFee(streitwert, feeData.ValueBased)
	courtTotal := fixedFee + valueBasedFee
	courtSME := math.Round(courtTotal * (1 - feeData.SMEReduction))
	recoverableCeiling := lookupRecoverableCosts(streitwert, feeData.RecoverableCosts)

	effectiveCourtFee := courtTotal
	if input.IsSME {
		effectiveCourtFee = courtSME
	}

	// InstanceTotal is the user's own outlay for this instance — court fee
	// only. RecoverableCeiling is the OPPOSING side's R.152 cost cap (a
	// worst-case loss-of-suit liability) and is exposed as its own line
	// item so the user sees worst-case exposure separately, not folded
	// into the GESAMTKOSTEN they themselves owe.
	return &UPCInstanceResult{
		Instance:           instanceKey,
		Label:              label,
		LabelEN:            labelEN,
		Enabled:            true,
		FixedFee:           fixedFee,
		ValueBasedFee:      valueBasedFee,
		CourtFeesTotal:     courtTotal,
		CourtFeesSME:       courtSME,
		RecoverableCeiling: recoverableCeiling,
		InstanceTotal:      effectiveCourtFee,
	}, nil
}

// ComputeEPAInstance returns the cost for an EPA proceeding.
func ComputeEPAInstance(input InstanceInput, instanceKey string) (*EPAInstanceResult, error) {
	epa, ok := EPAFees[instanceKey]
	if !ok {
		return nil, fmt.Errorf("unknown EPA instance: %s", instanceKey)
	}

	if !input.Enabled {
		return &EPAInstanceResult{
			Instance: instanceKey,
			Label:    epa.Label,
			LabelEN:  epa.LabelEN,
			Enabled:  false,
		}, nil
	}

	fee := epa.Fee
	if input.IsSME && epa.SMEFee > 0 {
		fee = epa.SMEFee
	}

	return &EPAInstanceResult{
		Instance:      instanceKey,
		Label:         epa.Label,
		LabelEN:       epa.LabelEN,
		Enabled:       true,
		Fee:           fee,
		IsSME:         input.IsSME,
		InstanceTotal: fee,
	}, nil
}

// Calculate processes a full CostRequest and returns a CostResponse.
func Calculate(req CostRequest) (*CostResponse, error) {
	resp := &CostResponse{}
	var totals CostTotals

	// DE instances
	allDE := AllDEInstances()
	for _, meta := range allDE {
		input, ok := req.Instances[meta.Key]
		if !ok {
			input = InstanceInput{Enabled: false}
		}
		result, err := ComputeDEInstance(req.Streitwert, input, meta, req.VATRate)
		if err != nil {
			return nil, err
		}
		resp.DEResults = append(resp.DEResults, *result)
		if result.Enabled {
			totals.CourtFees += result.CourtFee
			if result.Attorney != nil {
				totals.AttorneyFees += result.Attorney.BruttoTotal
			}
			if result.PatentAttorney != nil {
				totals.PatentAttorneyFees += result.PatentAttorney.BruttoTotal
			}
		}
	}

	// UPC instances
	for _, key := range []string{"UPC_FIRST", "UPC_APPEAL"} {
		input, ok := req.Instances[key]
		if !ok {
			input = InstanceInput{Enabled: false}
		}
		result, err := ComputeUPCInstance(req.Streitwert, input, key)
		if err != nil {
			return nil, err
		}
		resp.UPCResults = append(resp.UPCResults, *result)
		if result.Enabled {
			totals.CourtFees += result.InstanceTotal
		}
	}

	// EPA instances
	for _, key := range []string{"EPA_OPPOSITION", "EPA_APPEAL"} {
		input, ok := req.Instances[key]
		if !ok {
			input = InstanceInput{Enabled: false}
		}
		result, err := ComputeEPAInstance(input, key)
		if err != nil {
			return nil, err
		}
		resp.EPAResults = append(resp.EPAResults, *result)
		if result.Enabled {
			totals.EPAFees += result.InstanceTotal
		}
	}

	totals.GrandTotal = round2(totals.CourtFees + totals.AttorneyFees + totals.PatentAttorneyFees + totals.EPAFees)
	totals.CourtFees = round2(totals.CourtFees)
	totals.AttorneyFees = round2(totals.AttorneyFees)
	totals.PatentAttorneyFees = round2(totals.PatentAttorneyFees)
	totals.EPAFees = round2(totals.EPAFees)
	resp.Totals = totals

	return resp, nil
}

func round2(v float64) float64 {
	return math.Round(v*100) / 100
}
