package service

import (
    "encoding/json"
    "fmt"
    "strconv"
    "strings"
)

type ConfigProfileEditor struct {
    Name         string                        `json:"name"`
    Path         string                        `json:"path"`
    PrimaryTemplateName string                 `json:"primary_template_name"`
    BusinessName string                        `json:"business_name"`
    Description  string                        `json:"description"`
    OverlayName  string                        `json:"overlay_name"`
    DeviceCode   string                        `json:"device_code"`
    SiteName     string                        `json:"site_name"`
    Queue        ConfigProfileQueueEditor      `json:"queue"`
    Instances    []ConfigProfileInstanceEditor `json:"instances"`
    Raw          map[string]any                `json:"raw"`
}

type ConfigProfileQueueEditor struct {
    Size     string `json:"size"`
    Strategy string `json:"strategy"`
}

func DefaultConfigProfileQueue() ConfigProfileQueueEditor {
    return ConfigProfileQueueEditor{
        Size:     "8",
        Strategy: "drop_oldest",
    }
}

type InputBindingEditor struct {
    VideoSourceRef string `json:"video_source_ref"`
}

type ServiceBindingEditor struct {
    ServiceRef string `json:"service_ref"`
}

type OutputBindingEditor struct {
    PublishHLSPath  string `json:"publish_hls_path"`
    PublishRTSPPort string `json:"publish_rtsp_port"`
    PublishRTSPPath string `json:"publish_rtsp_path"`
    ChannelNo       string `json:"channel_no"`
}

type ConfigProfileInstanceEditor struct {
    Name            string                          `json:"name"`
    Template        string                          `json:"template"`
    VideoSourceRef  string                          `json:"video_source_ref"`
    DisplayName     string                          `json:"display_name"`
    SiteName        string                          `json:"site_name"`
    PublishHLSPath  string                          `json:"publish_hls_path"`
    PublishRTSPPort string                          `json:"publish_rtsp_port"`
    PublishRTSPPath string                          `json:"publish_rtsp_path"`
    ChannelNo       string                          `json:"channel_no"`
    InputBindings   map[string]InputBindingEditor   `json:"input_bindings,omitempty"`
    ServiceBindings map[string]ServiceBindingEditor `json:"service_bindings,omitempty"`
    OutputBindings  map[string]OutputBindingEditor  `json:"output_bindings,omitempty"`
    AdvancedParams  map[string]any                  `json:"advanced_params"`
    Delete          bool                            `json:"delete"`
}

func (s *ConfigPreviewService) GetProfileEditor(name string) (*ConfigProfileEditor, error) {
    raw, path, err := s.readAssetJSON("profiles", name)
    if err != nil {
        return nil, err
    }
    queueMap, _ := raw["queue"].(map[string]any)
    templateName := stringValue(raw["primary_template_name"])
    instances, siteName, deviceCode, err := s.loadRecognitionUnitEditors(name, templateName)
    if err != nil {
        return nil, err
    }
    return &ConfigProfileEditor{
        Name:                firstString(raw["name"], name),
        Path:                path,
        PrimaryTemplateName: stringValue(raw["primary_template_name"]),
        BusinessName:        stringValue(raw["business_name"]),
        Description:         stringValue(raw["description"]),
        OverlayName:         firstOverlayName(raw),
        DeviceCode:          deviceCode,
        SiteName:            siteName,
        Queue: ConfigProfileQueueEditor{
            Size:     valueString(queueMap["size"]),
            Strategy: stringValue(queueMap["strategy"]),
        },
        Instances: instances,
        Raw:       raw,
    }, nil
}

func (s *ConfigPreviewService) BuildProfileDocument(editor ConfigProfileEditor) (map[string]any, error) {
    name := strings.TrimSpace(editor.Name)
    if name == "" {
        return nil, fmt.Errorf("profile name is required")
    }
    if err := validateConfigName(name); err != nil {
        return nil, fmt.Errorf("invalid profile name: %w", err)
    }

    seen := map[string]struct{}{}
    instances := make([]map[string]any, 0, len(editor.Instances))
    for _, inst := range editor.Instances {
        if inst.Delete {
            continue
        }
        instanceName := strings.TrimSpace(inst.Name)
        if instanceName == "" {
            return nil, fmt.Errorf("instance name is required")
        }
        if err := validateConfigName(instanceName); err != nil {
            return nil, fmt.Errorf("invalid instance name %q: %w", instanceName, err)
        }
        if _, ok := seen[instanceName]; ok {
            return nil, fmt.Errorf("duplicate instance name: %s", instanceName)
        }
        seen[instanceName] = struct{}{}

        videoSourceRef := strings.TrimSpace(inputBindingRef(inst.InputBindings, "video_input_main"))
        if videoSourceRef == "" {
            videoSourceRef = strings.TrimSpace(inst.VideoSourceRef)
        }
        if videoSourceRef == "" {
            return nil, fmt.Errorf("video source is required for %s", instanceName)
        }

        params := map[string]any{}
        for key, value := range cloneMap(inst.AdvancedParams) {
            params[key] = value
        }

        instance := map[string]any{
            "name": instanceName,
        }
        if len(params) > 0 {
            instance["params"] = params
        }
        setString(instance, "template", inst.Template)
        sceneMeta := map[string]any{}
        setString(sceneMeta, "display_name", inst.DisplayName)
        setString(sceneMeta, "site_name", firstString(inst.SiteName, editor.SiteName))
        setString(sceneMeta, "device_code", editor.DeviceCode)
        if len(sceneMeta) > 0 {
            instance["scene_meta"] = sceneMeta
        }
        inputBindings := buildInputBindingDocument(inst)
        if len(inputBindings) > 0 {
            instance["input_bindings"] = inputBindings
        }
        serviceBindings := buildServiceBindingDocument(inst)
        if len(serviceBindings) > 0 {
            instance["service_bindings"] = serviceBindings
        }
        outputBindings, err := buildOutputBindingDocument(inst)
        if err != nil {
            return nil, err
        }
        if len(outputBindings) > 0 {
            instance["output_bindings"] = outputBindings
        }
        instances = append(instances, instance)
    }
    doc := map[string]any{
        "name":      name,
        "instances": instances,
    }
    setString(doc, "business_name", editor.BusinessName)
    setString(doc, "description", editor.Description)
    if overlayName := strings.TrimSpace(editor.OverlayName); overlayName != "" {
        doc["overlays"] = []any{overlayName}
    }
    normalizedQueue := editor.Queue
    if strings.TrimSpace(normalizedQueue.Size) == "" || strings.TrimSpace(normalizedQueue.Strategy) == "" {
        normalizedQueue = DefaultConfigProfileQueue()
    }
    queue := map[string]any{}
    if size := strings.TrimSpace(normalizedQueue.Size); size != "" {
        value, err := strconv.Atoi(size)
        if err != nil {
            return nil, fmt.Errorf("queue size must be a number")
        }
        queue["size"] = value
    }
    setString(queue, "strategy", normalizedQueue.Strategy)
    if len(queue) > 0 {
        doc["queue"] = queue
    }

    return doc, nil
}

func (s *ConfigPreviewService) SaveProfileEditor(editor ConfigProfileEditor) error {
    doc, err := s.buildSceneTemplateDocument(editor)
    if err != nil {
        return err
    }
    body, err := marshalConfigJSON(doc)
    if err != nil {
        return err
    }
    if s != nil && s.assets != nil {
    templateName := strings.TrimSpace(editor.PrimaryTemplateName)
    if templateName == "" {
        templateName = firstProfileTemplate(editor.Instances)
    }
    return s.assets.SaveProfile(
        strings.TrimSpace(editor.Name),
        templateName,
            strings.TrimSpace(editor.BusinessName),
            strings.TrimSpace(editor.Description),
            string(body),
        )
    }
    return fmt.Errorf("asset repository is not configured")
}

func (s *ConfigPreviewService) buildSceneTemplateDocument(editor ConfigProfileEditor) (map[string]any, error) {
    name := strings.TrimSpace(editor.Name)
    if name == "" {
        return nil, fmt.Errorf("scene template name is required")
    }
    if err := validateConfigName(name); err != nil {
        return nil, fmt.Errorf("invalid scene template name: %w", err)
    }
    doc := map[string]any{
        "name": name,
    }
    setString(doc, "primary_template_name", editor.PrimaryTemplateName)
    setString(doc, "business_name", editor.BusinessName)
    setString(doc, "description", editor.Description)
    setString(doc, "site_name", editor.SiteName)
    if overlayName := strings.TrimSpace(editor.OverlayName); overlayName != "" {
        doc["overlays"] = []any{overlayName}
    }
    normalizedQueue := editor.Queue
    if strings.TrimSpace(normalizedQueue.Size) == "" || strings.TrimSpace(normalizedQueue.Strategy) == "" {
        normalizedQueue = DefaultConfigProfileQueue()
    }
    queue := map[string]any{}
    if size := strings.TrimSpace(normalizedQueue.Size); size != "" {
        value, err := strconv.Atoi(size)
        if err != nil {
            return nil, fmt.Errorf("queue size must be a number")
        }
        queue["size"] = value
    }
    setString(queue, "strategy", normalizedQueue.Strategy)
    if len(queue) > 0 {
        doc["queue"] = queue
    }
    return doc, nil
}

func setString(m map[string]any, key string, value string) {
    if strings.TrimSpace(value) != "" {
        m[key] = strings.TrimSpace(value)
    }
}

func firstOverlayName(raw map[string]any) string {
    items, _ := raw["overlays"].([]any)
    for _, item := range items {
        if v := stringValue(item); strings.TrimSpace(v) != "" {
            return strings.TrimSpace(v)
        }
    }
    return ""
}

func marshalConfigJSON(doc map[string]any) ([]byte, error) {
    body, err := json.MarshalIndent(doc, "", "  ")
    if err != nil {
        return nil, err
    }
    return append(body, '\n'), nil
}

func firstProfileTemplate(instances []ConfigProfileInstanceEditor) string {
    for _, inst := range instances {
        if inst.Delete {
            continue
        }
        if v := strings.TrimSpace(inst.Template); v != "" {
            return v
        }
    }
    return ""
}

func parseInputBindingEditors(raw any) map[string]InputBindingEditor {
    items, _ := raw.(map[string]any)
    if len(items) == 0 {
        return nil
    }
    out := map[string]InputBindingEditor{}
    for key, value := range items {
        entry, _ := value.(map[string]any)
        out[key] = InputBindingEditor{
            VideoSourceRef: stringValue(entry["video_source_ref"]),
        }
    }
    return out
}

func parseServiceBindingEditors(raw any) map[string]ServiceBindingEditor {
    items, _ := raw.(map[string]any)
    if len(items) == 0 {
        return nil
    }
    out := map[string]ServiceBindingEditor{}
    for key, value := range items {
        entry, _ := value.(map[string]any)
        out[key] = ServiceBindingEditor{
            ServiceRef: stringValue(entry["service_ref"]),
        }
    }
    return out
}

func parseOutputBindingEditors(raw any) map[string]OutputBindingEditor {
    items, _ := raw.(map[string]any)
    if len(items) == 0 {
        return nil
    }
    out := map[string]OutputBindingEditor{}
    for key, value := range items {
        entry, _ := value.(map[string]any)
        out[key] = OutputBindingEditor{
            PublishHLSPath:  stringValue(entry["publish_hls_path"]),
            PublishRTSPPort: valueString(entry["publish_rtsp_port"]),
            PublishRTSPPath: stringValue(entry["publish_rtsp_path"]),
            ChannelNo:       stringValue(entry["channel_no"]),
        }
    }
    return out
}

func inputBindingRef(bindings map[string]InputBindingEditor, slot string) string {
    if len(bindings) == 0 {
        return ""
    }
    return strings.TrimSpace(bindings[slot].VideoSourceRef)
}

func outputBindingValue(bindings map[string]OutputBindingEditor, slot string, field string) string {
    if len(bindings) == 0 {
        return ""
    }
    item, ok := bindings[slot]
    if !ok {
        return ""
    }
    switch field {
    case "publish_hls_path":
        return strings.TrimSpace(item.PublishHLSPath)
    case "publish_rtsp_port":
        return strings.TrimSpace(item.PublishRTSPPort)
    case "publish_rtsp_path":
        return strings.TrimSpace(item.PublishRTSPPath)
    case "channel_no":
        return strings.TrimSpace(item.ChannelNo)
    default:
        return ""
    }
}

func buildInputBindingDocument(inst ConfigProfileInstanceEditor) map[string]any {
    out := map[string]any{}
    for key, value := range inst.InputBindings {
        entry := map[string]any{}
        setString(entry, "video_source_ref", value.VideoSourceRef)
        if len(entry) > 0 {
            out[key] = entry
        }
    }
    if strings.TrimSpace(inst.VideoSourceRef) != "" {
        if _, ok := out["video_input_main"]; !ok {
            out["video_input_main"] = map[string]any{"video_source_ref": strings.TrimSpace(inst.VideoSourceRef)}
        }
    }
    if len(out) == 0 {
        return nil
    }
    return out
}

func buildServiceBindingDocument(inst ConfigProfileInstanceEditor) map[string]any {
    out := map[string]any{}
    for key, value := range inst.ServiceBindings {
        entry := map[string]any{}
        setString(entry, "service_ref", value.ServiceRef)
        if len(entry) > 0 {
            out[key] = entry
        }
    }
    if len(out) == 0 {
        return nil
    }
    return out
}

func buildOutputBindingDocument(inst ConfigProfileInstanceEditor) (map[string]any, error) {
    out := map[string]any{}
    for key, value := range inst.OutputBindings {
        if key == "stream_output_main" {
            value = applyDefaultStreamOutputBinding(inst.Name, value)
        }
        entry, err := outputBindingEntry(value)
        if err != nil {
            return nil, err
        }
        if len(entry) > 0 {
            out[key] = entry
        }
    }
    if _, ok := out["stream_output_main"]; !ok {
        entry, err := outputBindingEntry(applyDefaultStreamOutputBinding(inst.Name, OutputBindingEditor{
            PublishHLSPath:  inst.PublishHLSPath,
            PublishRTSPPort: inst.PublishRTSPPort,
            PublishRTSPPath: inst.PublishRTSPPath,
            ChannelNo:       inst.ChannelNo,
        }))
        if err != nil {
            return nil, err
        }
        if len(entry) > 0 {
            out["stream_output_main"] = entry
        }
    }
    if len(out) == 0 {
        return nil, nil
    }
    return out, nil
}

func applyDefaultStreamOutputBinding(instanceName string, value OutputBindingEditor) OutputBindingEditor {
    name := strings.TrimSpace(instanceName)
    if name == "" {
        return value
    }
    if strings.TrimSpace(value.PublishHLSPath) == "" {
        value.PublishHLSPath = "./web/hls/" + name + "/index.m3u8"
    }
    if strings.TrimSpace(value.PublishRTSPPath) == "" {
        value.PublishRTSPPath = "/live/" + name
    }
    if strings.TrimSpace(value.ChannelNo) == "" {
        value.ChannelNo = name
    }
    return value
}

func outputBindingEntry(value OutputBindingEditor) (map[string]any, error) {
    entry := map[string]any{}
    setString(entry, "publish_hls_path", value.PublishHLSPath)
    setString(entry, "publish_rtsp_path", value.PublishRTSPPath)
    setString(entry, "channel_no", value.ChannelNo)
    if port := strings.TrimSpace(value.PublishRTSPPort); port != "" {
        parsed, err := strconv.Atoi(port)
        if err != nil {
            return nil, fmt.Errorf("publish rtsp port must be a number")
        }
        entry["publish_rtsp_port"] = parsed
    }
    return entry, nil
}
