// Package config는 services.json 설정 모델과 로더를 제공합니다.
package config

import (
	"encoding/json"
	"fmt"
	"os"
	"regexp"
)

// DBConn은 postgres/mysql 접속 정보를 항목별로 정의합니다.
// DSN 문자열을 직접 조립할 때 생기는 형식 오류·특수문자 문제를 없애기 위한 구조화 설정입니다.
type DBConn struct {
	Host     string `json:"host"` // 기본 localhost
	Port     int    `json:"port"` // 기본 postgres 5432 / mysql 3306
	User     string `json:"user"`
	Password string `json:"password"`
	DBName   string `json:"dbname"`  // postgres 기본 "postgres", mysql은 생략 가능
	SSLMode  string `json:"sslmode"` // postgres 전용, 기본 disable
}

// ServiceConfig는 services.json의 단일 서비스 인스턴스 정의입니다.
type ServiceConfig struct {
	Type      string `json:"type"`      // postgres|mysql|redis|nginx|fastapi|django|nodejs|nextjs|nuxtjs ...
	Name      string `json:"name"`      // 인스턴스 식별 라벨
	Runtime   string `json:"runtime"`   // native | docker (기본 native)
	Container string `json:"container"` // docker 컨테이너명 (runtime=docker)
	// 접속 힌트 (type별로 사용하는 값이 다름)
	DB        *DBConn `json:"db"`         // postgres/mysql 접속 정보 (항목별, 권장)
	DSN       string  `json:"dsn"`        // postgres/mysql 접속 문자열 (db 미지정 시 폴백)
	Addr      string  `json:"addr"`       // redis host:port
	Password  string  `json:"password"`   // redis 비밀번호
	URL       string  `json:"url"`        // http 프로브 대상 URL
	StatusURL string  `json:"status_url"` // nginx stub_status URL
	Port      int     `json:"port"`       // native 프로세스 리소스 조회용 리슨 포트
	Process   string  `json:"process"`    // native 프로세스 리소스 조회용 프로세스명
}

// envVarPattern은 services.json 값 안의 ${ENV_VAR} 참조를 찾습니다.
var envVarPattern = regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*)\}`)

// expandEnvVars는 ${VAR} 형식만 환경변수로 치환합니다.
// 미정의 변수는 원문을 그대로 남겨 설정 오류를 눈에 띄게 합니다. ($VAR 형식은 건드리지 않음)
func expandEnvVars(s string) string {
	return envVarPattern.ReplaceAllStringFunc(s, func(m string) string {
		name := m[2 : len(m)-1]
		if v, ok := os.LookupEnv(name); ok {
			return v
		}
		return m
	})
}

// Load는 services.json을 읽어 서비스 설정 목록을 반환합니다. 파일이 없으면 빈 목록.
// 값 안의 ${ENV_VAR}는 환경변수로 치환되므로 비밀번호를 파일에 평문으로 두지 않아도 됩니다.
func Load(path string) ([]ServiceConfig, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	var list []ServiceConfig
	if err := json.Unmarshal([]byte(expandEnvVars(string(data))), &list); err != nil {
		return nil, err
	}
	// runtime 기본값 보정
	for i := range list {
		if list[i].Runtime == "" {
			list[i].Runtime = "native"
		}
	}
	return list, nil
}

// Target은 시작 배너 표시용으로 서비스의 접속 대상을 요약합니다(비밀번호는 노출하지 않음).
func (s ServiceConfig) Target() string {
	switch s.Type {
	case "postgres", "mysql":
		if s.DB != nil {
			host, port, dbname := s.DB.Host, s.DB.Port, s.DB.DBName
			if host == "" {
				host = "localhost"
			}
			if port == 0 {
				if s.Type == "mysql" {
					port = 3306
				} else {
					port = 5432
				}
			}
			if dbname == "" && s.Type == "postgres" {
				dbname = "postgres"
			}
			return fmt.Sprintf("%s:%d/%s", host, port, dbname)
		}
		if s.DSN != "" {
			return "dsn 설정됨"
		}
	case "redis":
		return s.Addr
	case "nginx":
		if s.StatusURL != "" {
			return s.StatusURL
		}
		return s.URL
	default:
		return s.URL
	}
	return ""
}
