package db

import (
	"crypto/rand"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"sort"
	"strings"
	"time"
)

func (s *Store) scanFeedbackRow(scanner feedbackScanner) (Feedback, error) {
	return scanFeedback(scanner)
}

func feedbackSelectSQL() string {
	return `SELECT code, title, type, severity, category, priority, contact, body, status, status_detail, public_reply,
		note, assignee, handled_by, due_at, resolved_at, archived_at, sla_level, source_channel, risk_score, resolution,
		attachment, package_path, encrypted_package_path, package_sha256, plain_package_sha256, summary_text, included_files,
		mail_sent, remote_addr, tags, created_at, updated_at, last_activity_at FROM feedback_tickets`
}

func releaseNoticeSelectSQL() string {
	return `SELECT id, version, build, channel, title, message, release_notes, message_md, release_notes_md,
		download_url, notice_file, raw_json, published_at, created_at, updated_at FROM release_notices`
}

type feedbackScanner interface {
	Scan(dest ...any) error
}

func scanReleaseNotice(scanner interface{ Scan(dest ...any) error }) (ReleaseNotice, error) {
	var item ReleaseNotice
	err := scanner.Scan(&item.ID, &item.Version, &item.Build, &item.Channel, &item.Title, &item.Message,
		&item.ReleaseNotes, &item.MessageMD, &item.ReleaseNotesMD, &item.DownloadURL, &item.NoticeFile,
		&item.RawJSON, &item.PublishedAt, &item.CreatedAt, &item.UpdatedAt)
	return item, err
}

func scanFeedback(scanner feedbackScanner) (Feedback, error) {
	var item Feedback
	var mailSent int
	var tags string
	err := scanner.Scan(&item.Code, &item.Title, &item.Type, &item.Severity, &item.Category, &item.Priority, &item.Contact,
		&item.Body, &item.Status, &item.StatusDetail, &item.PublicReply, &item.Note, &item.Assignee, &item.HandledBy,
		&item.DueAt, &item.ResolvedAt, &item.ArchivedAt, &item.SLALevel, &item.SourceChannel, &item.RiskScore,
		&item.Resolution, &item.Attachment, &item.PackagePath, &item.EncryptedPackagePath, &item.PackageSha256,
		&item.PlainPackageSha256, &item.SummaryText, &item.IncludedFiles, &mailSent, &item.RemoteAddr, &tags,
		&item.CreatedAt, &item.UpdatedAt, &item.LastActivityAt)
	item.MailSent = mailSent == 1
	_ = json.Unmarshal([]byte(tags), &item.Tags)
	return item, err
}

func scanFeedbackRows(rows *sql.Rows) ([]Feedback, error) {
	items := []Feedback{}
	for rows.Next() {
		item, err := scanFeedback(rows)
		if err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, rows.Err()
}

func sourceSelectSQL() string {
	return `SELECT id, category_id, category_name, source_id, name, description, method, api_url, url_template, thumbnail_url,
		proxy_mode, timeout_ms, retry_count, cache_seconds, check_interval_sec, enabled, client_visible, supported_formats,
		last_status, last_latency_ms, last_checked_at, last_error, consecutive_failure, created_at, updated_at FROM source_endpoints`
}

func scanSourceRow(scanner sourceScanner) (Source, error) {
	return scanSource(scanner)
}

type sourceScanner interface {
	Scan(dest ...any) error
}

func scanSourceRowsCurrent(scanner sourceScanner) (Source, error) {
	return scanSource(scanner)
}

func scanSource(scanner sourceScanner) (Source, error) {
	var item Source
	var enabled, visible int
	err := scanner.Scan(&item.ID, &item.CategoryID, &item.CategoryName, &item.SourceID, &item.Name, &item.Description,
		&item.Method, &item.APIURL, &item.URLTemplate, &item.ThumbnailURL, &item.ProxyMode, &item.TimeoutMS, &item.RetryCount,
		&item.CacheSeconds, &item.CheckIntervalSec, &enabled, &visible, &item.SupportedFormats, &item.LastStatus,
		&item.LastLatencyMS, &item.LastCheckedAt, &item.LastError, &item.ConsecutiveFailure, &item.CreatedAt, &item.UpdatedAt)
	item.Enabled = enabled == 1
	item.ClientVisible = visible == 1
	return item, err
}

func scanAuditRows(rows *sql.Rows) ([]AuditLog, error) {
	items := []AuditLog{}
	for rows.Next() {
		var item AuditLog
		if err := rows.Scan(&item.ID, &item.Actor, &item.Type, &item.Target, &item.Message, &item.IP, &item.UserAgent, &item.CreatedAt); err != nil {
			return nil, err
		}
		items = append(items, item)
	}
	return items, rows.Err()
}

func feedbackWhere(filters FeedbackFilters) (string, []any) {
	clauses := []string{}
	args := []any{}
	if filters.Status != "" {
		clauses = append(clauses, "status = ?")
		args = append(args, filters.Status)
	}
	if filters.Category != "" {
		clauses = append(clauses, "category = ?")
		args = append(args, filters.Category)
	}
	if filters.Priority != "" {
		clauses = append(clauses, "priority = ?")
		args = append(args, filters.Priority)
	}
	if filters.Assignee != "" {
		clauses = append(clauses, "assignee = ?")
		args = append(args, filters.Assignee)
	}
	if filters.Query != "" {
		like := "%" + filters.Query + "%"
		clauses = append(clauses, "(code LIKE ? OR title LIKE ? OR contact LIKE ? OR body LIKE ?)")
		args = append(args, like, like, like, like)
	}
	if len(clauses) == 0 {
		return "", args
	}
	return " WHERE " + strings.Join(clauses, " AND "), args
}

func normalizePage(page, perPage int) (int, int) {
	if page < 1 {
		page = 1
	}
	if perPage <= 0 || perPage > 100 {
		perPage = 20
	}
	return page, perPage
}

func NewFeedbackCode() string {
	var data [3]byte
	if _, err := rand.Read(data[:]); err != nil {
		return "FB-" + time.Now().UTC().Format("20060102-150405")
	}
	return "FB-" + time.Now().UTC().Format("20060102") + "-" + strings.ToUpper(hex.EncodeToString(data[:]))
}

func Now() string {
	return time.Now().UTC().Format(time.RFC3339)
}

func sanitize(value string) string {
	return sanitizeLong(value, 1000)
}

func sanitizeLong(value string, max int) string {
	value = strings.TrimSpace(strings.ReplaceAll(value, "\x00", ""))
	value = strings.Map(func(r rune) rune {
		if r == '\n' || r == '\r' || r == '\t' {
			return r
		}
		if r < 32 {
			return -1
		}
		return r
	}, value)
	runes := []rune(value)
	if max > 0 && len(runes) > max {
		return string(runes[:max])
	}
	return value
}

func boolInt(value bool) int {
	if value {
		return 1
	}
	return 0
}

func normalizeCategory(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "suggestion", "ui", "other":
		return strings.ToLower(strings.TrimSpace(value))
	default:
		return "issue"
	}
}

func normalizePriority(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "major", "blocking":
		return strings.ToLower(strings.TrimSpace(value))
	default:
		return "normal"
	}
}

func defaultSLA(priority string) string {
	switch normalizePriority(priority) {
	case "blocking":
		return "urgent"
	case "major":
		return "elevated"
	default:
		return "standard"
	}
}

func defaultRisk(priority string) int {
	switch normalizePriority(priority) {
	case "blocking":
		return 90
	case "major":
		return 65
	default:
		return 30
	}
}

func normalizeTags(tags []string) []string {
	seen := map[string]bool{}
	out := []string{}
	for _, tag := range tags {
		tag = strings.ToLower(strings.Trim(strings.TrimSpace(tag), ",;#"))
		if tag == "" || seen[tag] {
			continue
		}
		runes := []rune(tag)
		if len(runes) > 32 {
			tag = string(runes[:32])
		}
		seen[tag] = true
		out = append(out, tag)
	}
	sort.Strings(out)
	return out
}

func normalizeProxyMode(value, category, name, url string) string {
	value = strings.ToLower(strings.TrimSpace(value))
	switch value {
	case "server_proxy", "proxy":
		return "server_proxy"
	case "disabled":
		return "disabled"
	case "client_direct", "direct":
		return "client_direct"
	}
	haystack := strings.ToLower(category + " " + name + " " + url)
	for _, token := range []string{"ip", "weather", "location", "定位", "天气"} {
		if strings.Contains(haystack, token) {
			return "client_direct"
		}
	}
	return "client_direct"
}

func firstNonEmpty(values ...string) string {
	for _, value := range values {
		if strings.TrimSpace(value) != "" {
			return strings.TrimSpace(value)
		}
	}
	return ""
}
