package client

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"

	"github.com/openeuler/Conch/internal/runtimeapi"
)

const (
	createTemplate  = "/api/template/create"
	pullTemplate    = "/api/template/pull"
	pushTemplate    = "/api/template/push"
	unpackTemplate  = "/api/template/unpack"
	listTemplates   = "/api/template/list"
	inspectTemplate = "/api/template/inspect"
	removeTemplate  = "/api/template/remove"
)

type TemplateNameRequest struct {
	Name string `json:"name"`
}

type TemplateListRequest struct {
	Origin   string `json:"origin,omitempty"`
	BootMode string `json:"boot_mode,omitempty"`
}

type TemplateRecord = runtimeapi.TemplateRecord

type templateListResponse struct {
	Items []TemplateRecord `json:"items"`
}

type TemplatePullRequest struct {
	Reference string            `json:"reference"`
	PlainHTTP bool              `json:"plain_http,omitempty"`
	Username  string            `json:"username,omitempty"`
	Password  string            `json:"password,omitempty"`
	Labels    map[string]string `json:"labels,omitempty"`
}

type TemplatePullResponse struct {
	Status       string `json:"status"`
	TemplateName string `json:"template_name"`
	TemplateID   string `json:"template_id"`
}

type TemplatePushRequest struct {
	Name            string `json:"name"`
	RemoteReference string `json:"remote_reference"`
	PlainHTTP       bool   `json:"plain_http,omitempty"`
	Username        string `json:"username,omitempty"`
	Password        string `json:"password,omitempty"`
}

type TemplateUnpackRequest struct {
	Name string `json:"name"`
}

type TemplateCreateRequest struct {
	Name       string
	Source     string
	KernelPath string
	InitrdPath string
	PlainHTTP  bool
	Username   string
	Password   string
	Labels     map[string]string
}

type TemplateCreateMetadata struct {
	Name      string            `json:"name"`
	Source    string            `json:"source"`
	PlainHTTP bool              `json:"plain_http,omitempty"`
	Username  string            `json:"username,omitempty"`
	Password  string            `json:"password,omitempty"`
	Labels    map[string]string `json:"labels,omitempty"`
}

type TemplateCreateResponse struct {
	Status       string `json:"status,omitempty"`
	TemplateName string `json:"template_name"`
	TemplateID   string `json:"template_id"`
}

func (c *Client) ListTemplates(ctx context.Context, req TemplateListRequest) ([]TemplateRecord, error) {
	var resp templateListResponse
	if err := c.postJSON(ctx, listTemplates, req, &resp); err != nil {
		return nil, err
	}
	return resp.Items, nil
}

func (c *Client) InspectTemplate(ctx context.Context, name string) (TemplateRecord, error) {
	var resp TemplateRecord
	if err := c.postJSON(ctx, inspectTemplate, TemplateNameRequest{Name: name}, &resp); err != nil {
		return TemplateRecord{}, err
	}
	return resp, nil
}

func (c *Client) RemoveTemplate(ctx context.Context, name string) error {
	return c.postJSON(ctx, removeTemplate, TemplateNameRequest{Name: name}, nil)
}

func (c *Client) PullTemplate(ctx context.Context, req TemplatePullRequest) (TemplatePullResponse, error) {
	var resp TemplatePullResponse
	if err := c.postJSON(ctx, pullTemplate, req, &resp); err != nil {
		return TemplatePullResponse{}, err
	}
	return resp, nil
}

func (c *Client) PushTemplate(ctx context.Context, req TemplatePushRequest) error {
	return c.postJSON(ctx, pushTemplate, req, nil)
}

func (c *Client) UnpackTemplate(ctx context.Context, req TemplateUnpackRequest) error {
	return c.postJSON(ctx, unpackTemplate, req, nil)
}

func (c *Client) CreateTemplate(ctx context.Context, req TemplateCreateRequest) (TemplateCreateResponse, error) {
	metadata, err := json.Marshal(TemplateCreateMetadata{
		Name:      req.Name,
		Source:    req.Source,
		PlainHTTP: req.PlainHTTP,
		Username:  req.Username,
		Password:  req.Password,
		Labels:    req.Labels,
	})
	if err != nil {
		return TemplateCreateResponse{}, fmt.Errorf("marshal template create metadata: %w", err)
	}
	var resp TemplateCreateResponse
	if err := c.postTemplateMultipart(ctx, req.KernelPath, req.InitrdPath, metadata, &resp); err != nil {
		return TemplateCreateResponse{}, err
	}
	return resp, nil
}

func (c *Client) postTemplateMultipart(ctx context.Context, kernelPath, initrdPath string, metadata []byte, out any) error {
	if kernelPath == "" {
		return fmt.Errorf("kernel path is required")
	}
	if initrdPath == "" {
		return fmt.Errorf("initrd path is required")
	}
	kernel, err := os.Open(kernelPath)
	if err != nil {
		return fmt.Errorf("open kernel: %w", err)
	}
	defer kernel.Close()
	initrd, err := os.Open(initrdPath)
	if err != nil {
		return fmt.Errorf("open initrd: %w", err)
	}
	defer initrd.Close()

	reader, writerPipe := io.Pipe()
	writer := multipart.NewWriter(writerPipe)
	go func() {
		writeErr := writeTemplateMultipart(writer, kernel, initrd, kernelPath, initrdPath, metadata)
		if writeErr != nil {
			_ = writerPipe.CloseWithError(writeErr)
			return
		}
		_ = writerPipe.Close()
	}()

	httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+createTemplate, reader)
	if err != nil {
		_ = reader.Close()
		return fmt.Errorf("create request %s: %w", createTemplate, err)
	}
	httpReq.Header.Set("Content-Type", writer.FormDataContentType())
	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		return fmt.Errorf("POST %s: %w", createTemplate, err)
	}
	defer resp.Body.Close()
	return decodeResponse(resp, createTemplate, out)
}

func writeTemplateMultipart(writer *multipart.Writer, kernel, initrd io.Reader, kernelPath, initrdPath string, metadata []byte) error {
	if err := writer.WriteField("metadata", string(metadata)); err != nil {
		return err
	}
	kernelPart, err := writer.CreateFormFile("kernel", filepath.Base(kernelPath))
	if err != nil {
		return err
	}
	if _, err := io.Copy(kernelPart, kernel); err != nil {
		return err
	}
	initrdPart, err := writer.CreateFormFile("initrd", filepath.Base(initrdPath))
	if err != nil {
		return err
	}
	if _, err := io.Copy(initrdPart, initrd); err != nil {
		return err
	}
	return writer.Close()
}