package lkgateway

import (
	"embed"
	"fmt"
	"html/template"
	"net/http"
	"path"
	"strings"
	"time"

	"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore"
)

//go:embed web/templates/*.html
var templatesFS embed.FS

// admin содержит по одному *template.Template на каждый view (layout +
// конкретный content-шаблон). Так html/template не путается с несколькими
// {{define "content"}} в разных файлах.
type admin struct {
	home, claims, claim, status, setup                                              *template.Template
	help, helpDatabase, helpLK, helpCrypto, helpSystems, helpRobot, helpArchitecture *template.Template
	wizard, news, keyWizard                                                         *template.Template
}

// templateFuncs — функции, доступные внутри шаблонов. Главная задача —
// русификация статусов и других технических обозначений (см. требование
// «всё UI на русском, кроме программных терминов»).
var templateFuncs = template.FuncMap{
	"ru":         russianText,
	"ruState":    russianState,
	"ruOutcome":  russianOutcome,
	"now":        time.Now,
	"add":         func(a, b int) int { return a + b },
	"fallbackTpl": fallback,
	"anyKeymedia": func(ds []flashDrive) bool {
		for _, d := range ds {
			if d.IsKeymedia {
				return true
			}
		}
		return false
	},
}

// russianState переводит технический FSM-state в человекочитаемый
// русский, сохраняя CSS-класс для бейджа.
func russianState(s string) string {
	switch s {
	case "draft":
		return "Черновик"
	case "validated":
		return "Валидирована"
	case "submitted_to_nsd":
		return "Отправлена в НРД"
	case "awaiting_decision":
		return "Ожидает решение"
	case "confirmed":
		return "Подтверждена"
	case "awaiting_sub16":
		return "Ожидает SUB16"
	case "done":
		return "Завершена"
	case "rejected":
		return "Отклонена"
	case "timed_out":
		return "Таймаут SLA"
	case "manual_approval":
		return "На ручном разборе"
	}
	return s
}

// russianOutcome — для NSDDecisionSecurity.Outcome.
func russianOutcome(o string) string {
	switch o {
	case "confirmed":
		return "Подтверждено"
	case "rejected":
		return "Отказ"
	}
	return o
}

// russianText — fallback функция для произвольных строк (на случай
// будущих расширений). Сейчас возвращает строку без изменений.
func russianText(s string) string { return s }

func newAdmin() (*admin, error) {
	parse := func(content string) (*template.Template, error) {
		return template.New("layout.html").Funcs(templateFuncs).ParseFS(templatesFS,
			"web/templates/layout.html",
			"web/templates/"+content)
	}
	home, err := parse("admin_home.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_home: %w", err)
	}
	claims, err := parse("admin_claims.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_claims: %w", err)
	}
	claim, err := parse("admin_claim.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_claim: %w", err)
	}
	status, err := parse("admin_status.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_status: %w", err)
	}
	setup, err := parse("admin_setup.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_setup: %w", err)
	}
	help, err := parse("admin_help.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_help: %w", err)
	}
	helpDB, err := parse("admin_help_database.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_help_database: %w", err)
	}
	helpLK, err := parse("admin_help_lk.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_help_lk: %w", err)
	}
	helpCrypto, err := parse("admin_help_crypto.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_help_crypto: %w", err)
	}
	helpSys, err := parse("admin_help_systems.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_help_systems: %w", err)
	}
	wizard, err := parse("admin_wizard.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_wizard: %w", err)
	}
	news, err := parse("admin_news.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_news: %w", err)
	}
	helpRobot, err := parse("admin_help_robot.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_help_robot: %w", err)
	}
	helpArch, err := parse("admin_help_architecture.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_help_architecture: %w", err)
	}
	keyWizard, err := parse("admin_keywizard.html")
	if err != nil {
		return nil, fmt.Errorf("parse admin_keywizard: %w", err)
	}
	return &admin{
		home: home, claims: claims, claim: claim, status: status, setup: setup,
		help: help, helpDatabase: helpDB, helpLK: helpLK, helpCrypto: helpCrypto, helpSystems: helpSys,
		helpRobot: helpRobot, helpArchitecture: helpArch,
		wizard: wizard, news: news, keyWizard: keyWizard,
	}, nil
}

// page — общий "конверт" данных для всех шаблонов.
type page struct {
	Title      string
	Active     string
	Now        string
	IsMockMode bool   // true если ИШ не настроен — bj-server в режиме эмуляции
	MockReason string // короткое описание почему mock
}

// globalRC — ссылка на runtime-конфиг для template-funcs/page helpers.
// Заполняется один раз в RegisterAdmin. Альтернатива — таскать rc через
// все renderXxx-функции, что шумно при широком фан-ауте.
var globalRC *RuntimeConfig

// homeData — данные дашборда.
type homeData struct {
	page
	Status SystemStatus
	Counts struct {
		Total      int
		Confirmed  int
		InProgress int
		Failed     int
	}
	Recent []ClaimView
	News   []NewsItem // top-3 активных или свежих новостей

	// Сводка готовности системы для hero-блока дашборда.
	AllReady      bool
	NotReadyCount int
	TotalCount    int
}

// claimsData — данные журнала.
type claimsData struct {
	page
	Items []ClaimView
}

// claimData — данные карточки.
type claimData struct {
	page
	Claim ClaimView
}

// statusData — данные страницы статуса.
type statusData struct {
	page
	Checks    []Status
	CheckedAt time.Time
}

// RegisterAdmin вешает HTML-маршруты /admin/* на mux. Возвращает admin
// со всеми загруженными шаблонами — вызывающий может прокинуть его в
// registerSetup для добавления вкладки «Настройка».
func RegisterAdmin(mux *http.ServeMux, svc *Service, rc *RuntimeConfig, getOpts func() CheckOptions) (*admin, error) {
	a, err := newAdmin()
	if err != nil {
		return nil, err
	}
	globalRC = rc

	mux.HandleFunc("/admin/", func(w http.ResponseWriter, r *http.Request) {
		p := strings.TrimPrefix(r.URL.Path, "/admin/")
		switch {
		case p == "" || p == "index" || p == "home":
			a.renderHome(w, r, svc, rc, getOpts())
		case p == "claims":
			a.renderClaims(w, r, svc)
		case strings.HasPrefix(p, "claims/"):
			id := strings.TrimPrefix(p, "claims/")
			a.renderClaim(w, r, svc, id)
		case p == "status":
			a.renderStatus(w, r, getOpts())
		case p == "help":
			render(w, a.help, nowPage("Инструкции", "help"))
		case p == "help/database":
			render(w, a.helpDatabase, nowPage("База данных", "help"))
		case p == "help/lk-api":
			render(w, a.helpLK, nowPage("API ЛК", "help"))
		case p == "help/crypto":
			render(w, a.helpCrypto, nowPage("Криптография", "help"))
		case p == "help/systems":
			render(w, a.helpSystems, nowPage("Внешние системы", "help"))
		case p == "help/robot":
			render(w, a.helpRobot, nowPage("Тестирование с роботом", "help"))
		case p == "help/architecture":
			render(w, a.helpArchitecture, nowPage("Архитектура обмена", "help"))
		default:
			http.NotFound(w, r)
		}
	})
	mux.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/admin/", http.StatusSeeOther)
	})
	return a, nil
}

func (a *admin) renderHome(w http.ResponseWriter, r *http.Request, svc *Service, rc *RuntimeConfig, opts CheckOptions) {
	ctx := r.Context()
	status := CheckAll(ctx, opts)
	recent, err := svc.ListClaims(ctx, m2mcore.Filter{Limit: 10})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	data := homeData{
		page:   nowPage("Дашборд", "home"),
		Status: status,
		Recent: recent.Items,
		News:   topNews(rc.Snapshot().News.Items, 3),
	}
	// Готовность системы считаем ТОЛЬКО по обязательным компонентам.
	// Опциональные (напр. callback в ЛК) не влияют на «готовность».
	for _, c := range status.Checks {
		if c.Optional {
			continue
		}
		data.TotalCount++
		if !c.OK {
			data.NotReadyCount++
		}
	}
	data.AllReady = data.TotalCount > 0 && data.NotReadyCount == 0
	full, err := svc.ListClaims(ctx, m2mcore.Filter{Limit: 200})
	if err == nil {
		for _, c := range full.Items {
			data.Counts.Total++
			switch c.Status {
			case string(m2mcore.StateConfirmed), string(m2mcore.StateAwaitingSUB16), string(m2mcore.StateDone):
				data.Counts.Confirmed++
			case string(m2mcore.StateRejected), string(m2mcore.StateTimedOut):
				data.Counts.Failed++
			default:
				data.Counts.InProgress++
			}
		}
	}
	render(w, a.home, data)
}

func (a *admin) renderClaims(w http.ResponseWriter, r *http.Request, svc *Service) {
	pageData, err := svc.ListClaims(r.Context(), m2mcore.Filter{Limit: 200})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	render(w, a.claims, claimsData{page: nowPage("Заявки", "claims"), Items: pageData.Items})
}

func (a *admin) renderClaim(w http.ResponseWriter, r *http.Request, svc *Service, id string) {
	id = path.Base(id)
	view, err := svc.GetClaim(r.Context(), id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusNotFound)
		return
	}
	render(w, a.claim, claimData{page: nowPage("Заявка", "claims"), Claim: view})
}

func (a *admin) renderStatus(w http.ResponseWriter, r *http.Request, opts CheckOptions) {
	s := CheckAll(r.Context(), opts)
	render(w, a.status, statusData{
		page: nowPage("Статус", "status"), Checks: s.Checks, CheckedAt: s.CheckedAt,
	})
}

func render(w http.ResponseWriter, t *template.Template, data any) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := t.ExecuteTemplate(w, "layout", data); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func nowPage(title, active string) page {
	p := page{Title: title, Active: active, Now: time.Now().Format("02.01.2006 15:04:05")}
	if globalRC != nil {
		s := globalRC.Snapshot()
		switch {
		case s.NSD.IGWBaseURL == "":
			p.IsMockMode = true
			p.MockReason = "ИШ НРД не настроен — заявки идут через внутренний mock (Decision эмитируется через 3 сек)"
		case s.Crypto.Provider == "" || s.Crypto.Provider == "stub":
			p.IsMockMode = true
			p.MockReason = "Провайдер СКЗИ = stub — подпись не делается, реальный обмен с НРД невозможен"
		}
	}
	return p
}

// topNews отбирает максимум N новостей: сначала те, что активны прямо сейчас
// (по ValidFrom..ValidTo), потом просто свежие. Скрытые (Dismissed) — мимо.
func topNews(items []NewsItem, n int) []NewsItem {
	now := time.Now()
	var active, rest []NewsItem
	for _, it := range items {
		if it.Dismissed {
			continue
		}
		isActive := !it.ValidFrom.IsZero() && !it.ValidTo.IsZero() &&
			now.After(it.ValidFrom) && now.Before(it.ValidTo)
		// «Будущие» окна с ValidFrom в будущем тоже считаем актуальными
		// (предупредить заранее).
		isUpcoming := !it.ValidFrom.IsZero() && now.Before(it.ValidFrom) &&
			it.ValidFrom.Sub(now) < 7*24*time.Hour
		if isActive || isUpcoming {
			active = append(active, it)
		} else {
			rest = append(rest, it)
		}
	}
	out := active
	if len(out) < n {
		need := n - len(out)
		if need > len(rest) {
			need = len(rest)
		}
		out = append(out, rest[:need]...)
	}
	if len(out) > n {
		out = out[:n]
	}
	return out
}
