package plaintext

import (
	"bufio"
	"fmt"
	"io"
	"strings"

	"github.com/Loyalsoldier/geoip/lib"
	"github.com/tidwall/gjson"
	"gopkg.in/yaml.v2"
)

type TextIn struct {
	Type        string
	Action      lib.Action
	Description string
	Name        string
	URI         string
	IPOrCIDR    []string
	InputDir    string
	Want        map[string]bool
	OnlyIPType  lib.IPType

	JSONPath             []string
	RemovePrefixesInLine []string
	RemoveSuffixesInLine []string
}

func (t *TextIn) scanFile(reader io.Reader, entry *lib.Entry) error {
	var err error
	switch t.Type {
	case TypeTextIn:
		err = t.scanFileForTextIn(reader, entry)
	case TypeJSONIn:
		err = t.scanFileForJSONIn(reader, entry)
	case TypeClashRuleSetClassicalIn:
		err = t.scanFileForClashClassicalRuleSetIn(reader, entry)
	case TypeClashRuleSetIPCIDRIn:
		err = t.scanFileForClashIPCIDRRuleSetIn(reader, entry)
	case TypeSurgeRuleSetIn:
		err = t.scanFileForSurgeRuleSetIn(reader, entry)
	default:
		return lib.ErrNotSupportedFormat
	}

	return err
}

func (t *TextIn) scanFileForTextIn(reader io.Reader, entry *lib.Entry) error {
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		line := scanner.Text()

		line, _, _ = strings.Cut(line, "#")
		line, _, _ = strings.Cut(line, "//")
		line, _, _ = strings.Cut(line, "/*")
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}

		line = strings.ToLower(line)
		for _, prefix := range t.RemovePrefixesInLine {
			line = strings.TrimSpace(strings.TrimPrefix(line, strings.ToLower(strings.TrimSpace(prefix))))
		}
		for _, suffix := range t.RemoveSuffixesInLine {
			line = strings.TrimSpace(strings.TrimSuffix(line, strings.ToLower(strings.TrimSpace(suffix))))
		}
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}

		if err := entry.AddPrefix(line); err != nil {
			return err
		}
	}
	if err := scanner.Err(); err != nil {
		return err
	}

	return nil
}

func (t *TextIn) readClashRuleSetYAMLFile(reader io.Reader) ([]string, error) {
	var payload struct {
		Payload []string `yaml:"payload"`
	}

	data, err := io.ReadAll(reader)
	if err != nil {
		return nil, err
	}

	if err := yaml.Unmarshal(data, &payload); err != nil {
		return nil, err
	}

	return payload.Payload, nil
}

func (t *TextIn) scanFileForClashIPCIDRRuleSetIn(reader io.Reader, entry *lib.Entry) error {
	payload, err := t.readClashRuleSetYAMLFile(reader)
	if err != nil {
		return err
	}

	for _, cidrStr := range payload {
		cidrStr = strings.TrimSpace(cidrStr)
		if cidrStr == "" {
			continue
		}
		if err := entry.AddPrefix(cidrStr); err != nil {
			return err
		}
	}

	return nil
}

func (t *TextIn) scanFileForClashClassicalRuleSetIn(reader io.Reader, entry *lib.Entry) error {
	payload, err := t.readClashRuleSetYAMLFile(reader)
	if err != nil {
		return err
	}

	for _, line := range payload {
		line = strings.ToLower(strings.TrimSpace(line))
		if line == "" {
			continue
		}

		// Examples:
		// IP-CIDR,162.208.16.0/24
		// IP-CIDR6,2a0b:e40:1::/48
		// IP-CIDR,162.208.16.0/24,no-resolve
		// IP-CIDR6,2a0b:e40:1::/48,no-resolve
		if strings.HasPrefix(line, "ip-cidr,") || strings.HasPrefix(line, "ip-cidr6,") {
			parts := strings.Split(line, ",")
			if len(parts) < 2 {
				continue
			}
			line = strings.TrimSpace(parts[1])
			if line == "" {
				continue
			}
			if err := entry.AddPrefix(line); err != nil {
				return err
			}
		}
	}

	return nil
}

func (t *TextIn) scanFileForSurgeRuleSetIn(reader io.Reader, entry *lib.Entry) error {
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		line := scanner.Text()

		line, _, _ = strings.Cut(line, "#")
		line, _, _ = strings.Cut(line, "//")
		line, _, _ = strings.Cut(line, "/*")
		line = strings.ToLower(strings.TrimSpace(line))
		if line == "" {
			continue
		}

		// Examples:
		// IP-CIDR,162.208.16.0/24
		// IP-CIDR6,2a0b:e40:1::/48
		// IP-CIDR,162.208.16.0/24,no-resolve
		// IP-CIDR6,2a0b:e40:1::/48,no-resolve
		if strings.HasPrefix(line, "ip-cidr,") || strings.HasPrefix(line, "ip-cidr6,") {
			parts := strings.Split(line, ",")
			if len(parts) < 2 {
				continue
			}
			line = strings.TrimSpace(parts[1])
			if line == "" {
				continue
			}
			if err := entry.AddPrefix(line); err != nil {
				return err
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return err
	}

	return nil
}

func (t *TextIn) scanFileForJSONIn(reader io.Reader, entry *lib.Entry) error {
	data, err := io.ReadAll(reader)
	if err != nil {
		return err
	}

	if !gjson.ValidBytes(data) {
		return fmt.Errorf("❌ [type %s | action %s] invalid JSON data", t.Type, t.Action)
	}

	// JSON Path syntax:
	// https://github.com/tidwall/gjson/blob/master/SYNTAX.md
	for _, path := range t.JSONPath {
		path = strings.TrimSpace(path)

		result := gjson.GetBytes(data, path)
		if err := t.processJSONResult(result, entry); err != nil {
			return fmt.Errorf("❌ [type %s | action %s] failed to process JSON: %v", t.Type, t.Action, err)
		}
	}

	return nil
}

func (t *TextIn) processJSONResult(result gjson.Result, entry *lib.Entry) error {
	switch {
	case !result.Exists():
		return fmt.Errorf("invaild IP address or CIDR (value not exist), please check your specified JSON path or JSON source")

	case result.Type == gjson.String:
		cidr := strings.TrimSpace(result.String())
		if cidr == "" {
			return fmt.Errorf("empty string, please check your specified JSON path or JSON source")
		}
		if err := entry.AddPrefix(cidr); err != nil {
			return err
		}

	case result.IsArray():
		for _, item := range result.Array() {
			if err := t.processJSONResult(item, entry); err != nil {
				return err
			}
		}

	default:
		return fmt.Errorf("invaild IP address or CIDR, please check your specified JSON path or JSON source")
	}

	return nil
}
