package tagger

import (
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strings"

	"gopkg.in/yaml.v3"
)

// TagResult contains all tags assigned to a document
type TagResult struct {
	DocType    string   `json:"doc_type"`
	Subjects   []string `json:"subjects"`
	SchoolLevel string  `json:"school_level"`
	State      string   `json:"state"`
	TrustScore float64  `json:"trust_score"`
}

// Tagger applies rules to content and URLs
type Tagger struct {
	docTypeRules  *DocTypeRules
	subjectRules  *SubjectRules
	levelRules    *LevelRules
	trustRules    *TrustRules
}

// DocTypeRules YAML structure
type DocTypeRules struct {
	DocTypes      map[string]DocTypeRule `yaml:"doc_types"`
	PriorityOrder []string               `yaml:"priority_order"`
}

type DocTypeRule struct {
	StrongTerms []string `yaml:"strong_terms"`
	MediumTerms []string `yaml:"medium_terms"`
	URLPatterns []string `yaml:"url_patterns"`
}

// SubjectRules YAML structure
type SubjectRules struct {
	Subjects    map[string]SubjectRule `yaml:"subjects"`
	Threshold   int                    `yaml:"threshold"`
	MaxSubjects int                    `yaml:"max_subjects"`
}

type SubjectRule struct {
	Strong   []string `yaml:"strong"`
	Weak     []string `yaml:"weak"`
	Negative []string `yaml:"negative"`
}

// LevelRules YAML structure
type LevelRules struct {
	Levels        map[string]LevelRule `yaml:"levels"`
	PriorityOrder []string             `yaml:"priority_order"`
}

type LevelRule struct {
	Strong   []string `yaml:"strong"`
	Weak     []string `yaml:"weak"`
	Negative []string `yaml:"negative"`
}

// TrustRules YAML structure
type TrustRules struct {
	DomainBoosts     []DomainBoost     `yaml:"domain_boosts"`
	TLDBoosts        []TLDBoost        `yaml:"tld_boosts"`
	Penalties        []Penalty         `yaml:"penalties"`
	ContentPenalties []ContentPenalty  `yaml:"content_penalties"`
}

type DomainBoost struct {
	Match  string  `yaml:"match"`
	Add    float64 `yaml:"add"`
	Reason string  `yaml:"reason"`
}

type TLDBoost struct {
	TLD    string  `yaml:"tld"`
	Add    float64 `yaml:"add"`
	Reason string  `yaml:"reason"`
}

type Penalty struct {
	IfURLContains []string `yaml:"if_url_contains"`
	Add           float64  `yaml:"add"`
	Reason        string   `yaml:"reason"`
}

type ContentPenalty struct {
	IfAdDensityGT     *float64 `yaml:"if_ad_density_gt,omitempty"`
	IfLinkDensityGT   *float64 `yaml:"if_link_density_gt,omitempty"`
	IfContentLengthLT *int     `yaml:"if_content_length_lt,omitempty"`
	Add               float64  `yaml:"add"`
	Reason            string   `yaml:"reason"`
}

// ContentFeatures for trust scoring
type ContentFeatures struct {
	AdDensity     float64
	LinkDensity   float64
	ContentLength int
}

// NewTagger creates a new tagger with rules from the specified directory
func NewTagger(rulesDir string) (*Tagger, error) {
	t := &Tagger{}

	// Load doc type rules
	docTypeBytes, err := os.ReadFile(filepath.Join(rulesDir, "doc_type_rules.yaml"))
	if err != nil {
		return nil, err
	}
	t.docTypeRules = &DocTypeRules{}
	if err := yaml.Unmarshal(docTypeBytes, t.docTypeRules); err != nil {
		return nil, err
	}

	// Load subject rules
	subjectBytes, err := os.ReadFile(filepath.Join(rulesDir, "subject_rules.yaml"))
	if err != nil {
		return nil, err
	}
	t.subjectRules = &SubjectRules{}
	if err := yaml.Unmarshal(subjectBytes, t.subjectRules); err != nil {
		return nil, err
	}

	// Load level rules
	levelBytes, err := os.ReadFile(filepath.Join(rulesDir, "level_rules.yaml"))
	if err != nil {
		return nil, err
	}
	t.levelRules = &LevelRules{}
	if err := yaml.Unmarshal(levelBytes, t.levelRules); err != nil {
		return nil, err
	}

	// Load trust rules
	trustBytes, err := os.ReadFile(filepath.Join(rulesDir, "trust_rules.yaml"))
	if err != nil {
		return nil, err
	}
	t.trustRules = &TrustRules{}
	if err := yaml.Unmarshal(trustBytes, t.trustRules); err != nil {
		return nil, err
	}

	return t, nil
}

// Tag applies all rules to content and returns tags
func (t *Tagger) Tag(url string, title string, content string, features ContentFeatures) TagResult {
	lowerURL := strings.ToLower(url)
	lowerTitle := strings.ToLower(title)
	lowerContent := strings.ToLower(content)
	combined := lowerTitle + " " + lowerContent

	result := TagResult{
		DocType:     "Sonstiges",
		Subjects:    []string{},
		SchoolLevel: "NA",
		State:       t.detectState(lowerURL),
	}

	// Tag doc type
	result.DocType = t.tagDocType(lowerURL, combined)

	// Tag subjects
	result.Subjects = t.tagSubjects(combined)

	// Tag school level
	result.SchoolLevel = t.tagSchoolLevel(combined)

	// Calculate trust score
	result.TrustScore = t.calculateTrustScore(lowerURL, features)

	return result
}

func (t *Tagger) tagDocType(url string, content string) string {
	scores := make(map[string]int)

	for docType, rule := range t.docTypeRules.DocTypes {
		score := 0

		// Check strong terms (+4 each)
		for _, term := range rule.StrongTerms {
			if strings.Contains(content, strings.ToLower(term)) {
				score += 4
			}
		}

		// Check medium terms (+3 each)
		for _, term := range rule.MediumTerms {
			if strings.Contains(content, strings.ToLower(term)) {
				score += 3
			}
		}

		// Check URL patterns (+2 each)
		for _, pattern := range rule.URLPatterns {
			if strings.Contains(url, strings.ToLower(pattern)) {
				score += 2
			}
		}

		if score > 0 {
			scores[docType] = score
		}
	}

	if len(scores) == 0 {
		return "Sonstiges"
	}

	// Find highest scoring type, respecting priority for ties
	var bestType string
	bestScore := 0

	for _, docType := range t.docTypeRules.PriorityOrder {
		if score, ok := scores[docType]; ok {
			if score > bestScore || (score == bestScore && bestType == "") {
				bestScore = score
				bestType = docType
			}
		}
	}

	if bestType == "" {
		return "Sonstiges"
	}

	return bestType
}

func (t *Tagger) tagSubjects(content string) []string {
	type subjectScore struct {
		name  string
		score int
	}

	var scores []subjectScore

	for subject, rule := range t.subjectRules.Subjects {
		score := 0

		// Check strong terms (+3 each)
		for _, term := range rule.Strong {
			if strings.Contains(content, strings.ToLower(term)) {
				score += 3
			}
		}

		// Check weak terms (+1 each)
		for _, term := range rule.Weak {
			if strings.Contains(content, strings.ToLower(term)) {
				score += 1
			}
		}

		// Check negative terms (-2 each)
		for _, term := range rule.Negative {
			if strings.Contains(content, strings.ToLower(term)) {
				score -= 2
			}
		}

		threshold := t.subjectRules.Threshold
		if threshold == 0 {
			threshold = 4 // default
		}

		if score >= threshold {
			scores = append(scores, subjectScore{name: subject, score: score})
		}
	}

	// Sort by score descending
	sort.Slice(scores, func(i, j int) bool {
		return scores[i].score > scores[j].score
	})

	// Take top N subjects
	maxSubjects := t.subjectRules.MaxSubjects
	if maxSubjects == 0 {
		maxSubjects = 3 // default
	}

	var result []string
	for i, s := range scores {
		if i >= maxSubjects {
			break
		}
		result = append(result, s.name)
	}

	return result
}

func (t *Tagger) tagSchoolLevel(content string) string {
	scores := make(map[string]int)

	for level, rule := range t.levelRules.Levels {
		score := 0

		// Check strong terms (+3 each)
		for _, term := range rule.Strong {
			if strings.Contains(content, strings.ToLower(term)) {
				score += 3
			}
		}

		// Check weak terms (+1 each)
		for _, term := range rule.Weak {
			if strings.Contains(content, strings.ToLower(term)) {
				score += 1
			}
		}

		// Check negative terms (-2 each)
		for _, term := range rule.Negative {
			if strings.Contains(content, strings.ToLower(term)) {
				score -= 2
			}
		}

		if score > 0 {
			scores[level] = score
		}
	}

	if len(scores) == 0 {
		return "NA"
	}

	// Find highest scoring level, respecting priority for ties
	var bestLevel string
	bestScore := 0

	for _, level := range t.levelRules.PriorityOrder {
		if score, ok := scores[level]; ok {
			if score > bestScore {
				bestScore = score
				bestLevel = level
			}
		}
	}

	if bestLevel == "" {
		return "NA"
	}

	return bestLevel
}

func (t *Tagger) calculateTrustScore(url string, features ContentFeatures) float64 {
	score := 0.50 // base score

	// Apply domain boosts
	for _, boost := range t.trustRules.DomainBoosts {
		if matchDomainPattern(url, boost.Match) {
			score += boost.Add
		}
	}

	// Apply TLD boosts
	for _, boost := range t.trustRules.TLDBoosts {
		if strings.HasSuffix(url, boost.TLD) || strings.Contains(url, boost.TLD+"/") {
			score += boost.Add
		}
	}

	// Apply URL penalties
	for _, penalty := range t.trustRules.Penalties {
		for _, pattern := range penalty.IfURLContains {
			if strings.Contains(url, strings.ToLower(pattern)) {
				score += penalty.Add // Add is negative
				break
			}
		}
	}

	// Apply content penalties
	for _, penalty := range t.trustRules.ContentPenalties {
		if penalty.IfAdDensityGT != nil && features.AdDensity > *penalty.IfAdDensityGT {
			score += penalty.Add
		}
		if penalty.IfLinkDensityGT != nil && features.LinkDensity > *penalty.IfLinkDensityGT {
			score += penalty.Add
		}
		if penalty.IfContentLengthLT != nil && features.ContentLength < *penalty.IfContentLengthLT {
			score += penalty.Add
		}
	}

	// Clamp to [0, 1]
	if score < 0 {
		score = 0
	}
	if score > 1 {
		score = 1
	}

	return score
}

func matchDomainPattern(url string, pattern string) bool {
	// Convert wildcard pattern to regex
	// *.example.de should match subdomain.example.de and example.de
	regexPattern := strings.ReplaceAll(pattern, ".", "\\.")
	regexPattern = strings.ReplaceAll(regexPattern, "*", ".*")
	regexPattern = "(?i)" + regexPattern // case insensitive

	re, err := regexp.Compile(regexPattern)
	if err != nil {
		return false
	}

	return re.MatchString(url)
}

func (t *Tagger) detectState(url string) string {
	statePatterns := map[string][]string{
		"BW":  {"baden-wuerttemberg", "bw.de", "schule-bw.de", "kultusministerium.baden"},
		"BY":  {"bayern.de", "isb.bayern", "km.bayern"},
		"BE":  {"berlin.de", "bildungsserver.berlin"},
		"BB":  {"brandenburg.de", "bildungsserver.brandenburg"},
		"HB":  {"bremen.de", "lis.bremen"},
		"HH":  {"hamburg.de", "li.hamburg"},
		"HE":  {"hessen.de", "hkm.hessen", "bildung.hessen"},
		"MV":  {"mecklenburg-vorpommern", "mv.de", "bildung-mv.de"},
		"NI":  {"niedersachsen.de", "nibis.de", "mk.niedersachsen"},
		"NW":  {"nrw.de", "learnline.nrw", "schulministerium.nrw"},
		"RP":  {"rheinland-pfalz", "rlp.de", "bildung-rp.de"},
		"SL":  {"saarland.de", "bildungsserver.saarland"},
		"SN":  {"sachsen.de", "schule.sachsen", "smk.sachsen"},
		"ST":  {"sachsen-anhalt", "bildung-lsa.de", "mk.sachsen-anhalt"},
		"SH":  {"schleswig-holstein", "sh.de", "bildungsserver.schleswig"},
		"TH":  {"thueringen.de", "schulportal-thueringen"},
	}

	for state, patterns := range statePatterns {
		for _, pattern := range patterns {
			if strings.Contains(url, pattern) {
				return state
			}
		}
	}

	return "" // Bundesweit or unknown
}
