package lkgateway

import (
	"context"
	"errors"
	"net"
	"net/http"

	"github.com/jackc/pgx/v5/pgxpool"
	"os"
	"time"
)

// Status — состояние одной проверяемой подсистемы.
type Status struct {
	Name    string `json:"name"`
	OK      bool   `json:"ok"`
	Message string `json:"message,omitempty"`
	Detail  string `json:"detail,omitempty"`
	// Optional — компонент не обязателен для работы с НРД. Его «не-OK»
	// не делает систему «не готовой» (напр. callback в ЛК).
	Optional bool `json:"optional,omitempty"`
}

// SystemStatus — все проверки.
type SystemStatus struct {
	Profile   string    `json:"profile"`
	Provider  string    `json:"crypto_provider"`
	Checks    []Status  `json:"checks"`
	CheckedAt time.Time `json:"checked_at"`
}

// CheckOptions — что и как проверять.
type CheckOptions struct {
	PostgresDSN    string        // если пусто — режим in-memory, проверки нет
	CryptoSocket   string        // путь до UDS crypto-service
	NSDAdapterURL  string        // например http://127.0.0.1:8082
	LKCallbackURL  string        // куда шлём callback (lk-emulator)
	Profile        string        // имя профиля nsdadapter (guest-gost...)
	CryptoProvider string        // BJ_CRYPTO_PROVIDER (stub|cryptopro|...)
	Timeout        time.Duration // таймаут на одну проверку
}

// CheckAll выполняет все доступные проверки и возвращает SystemStatus.
func CheckAll(ctx context.Context, o CheckOptions) SystemStatus {
	if o.Timeout == 0 {
		o.Timeout = 2 * time.Second
	}
	out := SystemStatus{
		Profile:   o.Profile,
		Provider:  o.CryptoProvider,
		CheckedAt: time.Now().UTC(),
	}

	out.Checks = append(out.Checks, checkPostgres(ctx, o))
	out.Checks = append(out.Checks, checkCryptoSocket(o))
	out.Checks = append(out.Checks, checkNSDAdapter(ctx, o))
	out.Checks = append(out.Checks, checkLKCallback(ctx, o))

	return out
}

func checkPostgres(ctx context.Context, o CheckOptions) Status {
	s := Status{Name: "База данных PostgreSQL"}
	if o.PostgresDSN == "" {
		s.OK = true
		s.Optional = true
		s.Message = "in-memory — данные не сохраняются между перезапусками"
		return s
	}
	pctx, cancel := context.WithTimeout(ctx, o.Timeout)
	defer cancel()
	pool, err := pgxpool.New(pctx, o.PostgresDSN)
	if err != nil {
		s.OK = false
		s.Message = "ошибка подключения: " + err.Error()
		return s
	}
	defer pool.Close()
	if err := pool.Ping(pctx); err != nil {
		s.OK = false
		s.Message = "не отвечает: " + err.Error()
		return s
	}
	s.OK = true
	s.Message = "подключена, репозиторий m2m_core.deals"
	return s
}

func checkCryptoSocket(o CheckOptions) Status {
	s := Status{Name: "crypto-service (UDS)"}
	if o.CryptoSocket == "" {
		s.OK = false
		s.Message = "BJ_CRYPTO_SOCKET не задан"
		return s
	}
	info, err := os.Stat(o.CryptoSocket)
	if err != nil {
		s.OK = false
		s.Message = "сокет недоступен"
		s.Detail = err.Error()
		return s
	}
	if info.Mode()&os.ModeSocket == 0 {
		s.OK = false
		s.Message = "путь существует, но это не сокет"
		s.Detail = o.CryptoSocket
		return s
	}
	// Пробуем подключиться.
	d := net.Dialer{Timeout: o.Timeout}
	conn, err := d.Dial("unix", o.CryptoSocket)
	if err != nil {
		s.OK = false
		s.Message = "сокет существует, но не отвечает"
		s.Detail = err.Error()
		return s
	}
	_ = conn.Close()
	s.OK = true
	s.Message = "сокет открыт"
	s.Detail = o.CryptoSocket
	if o.CryptoProvider == "stub" || o.CryptoProvider == "" {
		s.Message += ", провайдер stub (реальная криптография не подключена)"
	} else {
		s.Message += ", провайдер " + o.CryptoProvider
	}
	return s
}

func checkNSDAdapter(ctx context.Context, o CheckOptions) Status {
	s := Status{Name: "Интеграционный шлюз НРД"}
	if o.NSDAdapterURL == "" {
		s.OK = true
		s.Optional = true
		s.Message = "не подключён — режим эмуляции (mock)"
		return s
	}
	// У ИШ нет /healthz — проверяем рабочий эндпоинт Web API (engine/state
	// отвечает 200 «Running», когда движок поднят).
	st := httpHealth(ctx, o.NSDAdapterURL+"/api/admin/engine/state", o.Timeout, s)
	if st.OK {
		st.Message = "подключён, движок работает"
	}
	return st
}

func checkLKCallback(ctx context.Context, o CheckOptions) Status {
	s := Status{Name: "Callback в личный кабинет", Optional: true}
	if o.LKCallbackURL == "" {
		s.OK = false
		s.Message = "не настроен — уведомления в ЛК отключены (необязательно для работы с НРД)"
		return s
	}
	return httpHealth(ctx, o.LKCallbackURL+"/healthz", o.Timeout, s)
}

func httpHealth(ctx context.Context, url string, timeout time.Duration, s Status) Status {
	c := &http.Client{Timeout: timeout}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		s.OK = false
		s.Message = "не получилось собрать запрос"
		s.Detail = err.Error()
		return s
	}
	resp, err := c.Do(req)
	if err != nil {
		s.OK = false
		s.Message = "недоступен"
		s.Detail = err.Error()
		return s
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		s.OK = false
		s.Message = "HTTP " + http.StatusText(resp.StatusCode)
		s.Detail = url
		return s
	}
	s.OK = true
	s.Message = "OK"
	s.Detail = url
	return s
}

// ErrUnknown — общий placeholder.
var ErrUnknown = errors.New("lkgateway: unknown error")
