package subscription

import (
	"context"
	"encoding/base64"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"
	"unicode"

	"vpnclient/internal/config"
	"vpnclient/internal/linknorm"
	"vpnclient/internal/protocols/hysteria2"
	"vpnclient/internal/protocols/naive"
)

// Item is one imported share link.
type Item struct {
	Name     string
	Protocol config.Protocol
	URI      string
}

// Fetch downloads a subscription body and parses proxy share links.
func Fetch(ctx context.Context, rawURL string) ([]Item, error) {
	rawURL = strings.TrimSpace(rawURL)
	if rawURL == "" {
		return nil, fmt.Errorf("пустой URL подписки")
	}
	u, err := url.Parse(rawURL)
	if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
		return nil, fmt.Errorf("нужен http(s) URL подписки")
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", "Navis/1.3.0")
	client := &http.Client{Timeout: 45 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("подписка HTTP %d", resp.StatusCode)
	}
	body, err := io.ReadAll(io.LimitReader(resp.Body, 8<<20))
	if err != nil {
		return nil, err
	}
	return ParseBody(string(body))
}

// ParseBody accepts plain text lines or base64 (Clash/V2Ray style subscription).
func ParseBody(body string) ([]Item, error) {
	body = strings.TrimSpace(body)
	if body == "" {
		return nil, fmt.Errorf("пустая подписка")
	}
	text := body
	if !looksLikeLinks(body) {
		if decoded, err := decodeMaybeBase64(body); err == nil && looksLikeLinks(decoded) {
			text = decoded
		}
	}
	lines := splitLines(text)
	out := make([]Item, 0, len(lines))
	seen := map[string]int{}
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		// Some panels wrap links in quotes
		line = strings.Trim(line, `"'`)
		proto := config.DetectProtocol(line)
		if proto == "" {
			continue
		}
		normalized, detected, remark, err := linknorm.Normalize(proto, line)
		if err != nil {
			continue
		}
		name := remark
		if name == "" {
			name = defaultName(detected, normalized)
		}
		base := name
		for n := 2; ; n++ {
			if _, ok := seen[name]; !ok {
				break
			}
			name = fmt.Sprintf("%s-%d", base, n)
		}
		seen[name] = 1
		out = append(out, Item{Name: name, Protocol: detected, URI: normalized})
	}
	if len(out) == 0 {
		return nil, fmt.Errorf("в подписке нет поддерживаемых ссылок (naive / hysteria2)")
	}
	return out, nil
}

func looksLikeLinks(s string) bool {
	lower := strings.ToLower(s)
	return strings.Contains(lower, "hysteria2://") ||
		strings.Contains(lower, "hy2://") ||
		strings.Contains(lower, "naive+") ||
		strings.Contains(lower, "https://") ||
		strings.Contains(lower, "quic://")
}

func decodeMaybeBase64(s string) (string, error) {
	s = strings.Map(func(r rune) rune {
		if unicode.IsSpace(r) {
			return -1
		}
		return r
	}, s)
	raw, err := base64.StdEncoding.DecodeString(s)
	if err != nil {
		raw, err = base64.RawStdEncoding.DecodeString(s)
	}
	if err != nil {
		raw, err = base64.URLEncoding.DecodeString(s)
	}
	if err != nil {
		return "", err
	}
	return string(raw), nil
}

func splitLines(s string) []string {
	s = strings.ReplaceAll(s, "\r\n", "\n")
	s = strings.ReplaceAll(s, "\r", "\n")
	return strings.Split(s, "\n")
}

func defaultName(proto config.Protocol, uri string) string {
	host := ""
	switch proto {
	case config.ProtocolHysteria2:
		if h, _, err := hysteria2.HostPort(uri); err == nil {
			host = h
		}
	default:
		if u, _, err := naive.ParseShareLink(uri); err == nil {
			if parsed, err := url.Parse(u); err == nil {
				host = parsed.Hostname()
			}
		}
	}
	if host == "" {
		host = string(proto)
	}
	return string(proto) + "-" + host
}
