package operator

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"go.yaml.in/yaml/v3"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agentview"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

// apiOperator implements Operator by calling the daemon's HTTP API.
// Used when the daemon is running.

func (a *apiOperator) Agents() AgentOperator { return &apiAgentOperator{apiOperator: a} }
func (a *apiOperator) Servers() ServerOperator {
	return &apiServerOperator{apiOperator: a}
}
func (a *apiOperator) Skills() SkillOperator { return &apiSkillOperator{apiOperator: a} }

const apiPrefix = "/api/internal"
const serverConfigAPIPath = apiPrefix + "/servers"

// ===========================================================================
// Agent
// ===========================================================================

type apiAgentOperator struct {
	*apiOperator
}

func (a *apiAgentOperator) Get(id string) (agentview.Snapshot, bool, error) {
	var resp agentview.Snapshot
	if err := a.doGet(apiPrefix+"/agents/"+id, &resp); err != nil {
		if isNotFound(err) {
			return agentview.Snapshot{}, false, nil
		}
		return agentview.Snapshot{}, false, err
	}
	return resp, true, nil
}

func (a *apiAgentOperator) All() ([]agentview.Snapshot, error) {
	var resp []agentview.Snapshot
	if err := a.doGet(apiPrefix+"/agents", &resp); err != nil {
		return nil, err
	}
	return resp, nil
}

// ===========================================================================
// Server
// ===========================================================================

type apiServerOperator struct {
	*apiOperator
}

func (a *apiServerOperator) Get(id string) (profile.AgentTeamServer, bool, error) {
	var resp dto.ServerResponse
	if err := a.doGet(serverConfigAPIPath+"/"+id, &resp); err != nil {
		if isNotFound(err) {
			return profile.AgentTeamServer{}, false, nil
		}
		return profile.AgentTeamServer{}, false, err
	}
	return serverFromResponse(resp), true, nil
}

func (a *apiServerOperator) All() ([]profile.AgentTeamServer, error) {
	var resp []dto.ServerResponse
	if err := a.doGet(serverConfigAPIPath, &resp); err != nil {
		return nil, err
	}
	result := make([]profile.AgentTeamServer, len(resp))
	for i, r := range resp {
		result[i] = serverFromResponse(r)
	}
	return result, nil
}

func (a *apiServerOperator) Add(server *profile.AgentTeamServer) (profile.AgentTeamServer, error) {
	req := serverToRequest(*server)
	var resp dto.ServerResponse
	if err := a.doPost(serverConfigAPIPath, req, &resp); err != nil {
		return profile.AgentTeamServer{}, err
	}
	return serverFromResponse(resp), nil
}

func (a *apiServerOperator) Update(server profile.AgentTeamServer) (profile.AgentTeamServer, error) {
	req := serverToRequest(server)
	var resp dto.ServerResponse
	if err := a.doPut(serverConfigAPIPath+"/"+server.ID, req, &resp); err != nil {
		return profile.AgentTeamServer{}, err
	}
	return serverFromResponse(resp), nil
}

func (a *apiServerOperator) Delete(id string) error {
	return a.doDelete(serverConfigAPIPath + "/" + id)
}

func (a *apiServerOperator) SetEnabled(id string, enabled bool) error {
	server, ok, err := a.Get(id)
	if err != nil {
		return err
	}
	if !ok {
		return fmt.Errorf("server %q not found", id)
	}
	server.Enabled = enabled
	_, err = a.Update(server)
	return err
}

func serverFromResponse(resp dto.ServerResponse) profile.AgentTeamServer {
	var cfgYAML types.RawMessage
	if len(resp.Config) > 0 {
		data, _ := yaml.Marshal(resp.Config)
		cfgYAML = data
	}
	return profile.AgentTeamServer{
		ID:          resp.ID,
		Enabled:     resp.Enabled,
		Name:        resp.Name,
		Description: resp.Description,
		AgentID:     resp.AgentID,
		Config:      cfgYAML,
	}
}

func serverToRequest(server profile.AgentTeamServer) dto.ServerRequest {
	var cfg map[string]interface{}
	if len(server.Config) > 0 {
		_ = yaml.Unmarshal(server.Config, &cfg)
	}
	return dto.ServerRequest{
		ID:          server.ID,
		Enabled:     server.Enabled,
		Name:        server.Name,
		Description: &server.Description,
		AgentID:     server.AgentID,
		Config:      cfg,
	}
}

// ===========================================================================
// Skill
// ===========================================================================

type apiSkillOperator struct {
	*apiOperator
}

func (a *apiSkillOperator) Get(id string) (profile.Skill, bool, error) {
	var resp dto.SkillResponse
	if err := a.doGet(apiPrefix+"/skills/"+id, &resp); err != nil {
		if isNotFound(err) {
			return profile.Skill{}, false, nil
		}
		return profile.Skill{}, false, err
	}
	return skillFromResponse(resp), true, nil
}

func (a *apiSkillOperator) All() ([]profile.Skill, error) {
	var resp []dto.SkillResponse
	if err := a.doGet(apiPrefix+"/skills", &resp); err != nil {
		return nil, err
	}
	result := make([]profile.Skill, len(resp))
	for i, r := range resp {
		result[i] = skillFromResponse(r)
	}
	return result, nil
}

func (a *apiSkillOperator) Add(s profile.Skill) (profile.Skill, error) {
	// Skills don't have a simple JSON create endpoint; use import with a temp file approach.
	// But for the API, we send the skill metadata as a PUT after import.
	// Actually, for the operator pattern, Add is used internally by ImportFromFile/ImportFromURL
	// on the server side. The CLI skill add flow goes through Import. We provide Add here
	// for completeness but it's not typically used directly.
	req := skillToUpdateRequest(s)
	var resp dto.SkillResponse
	if err := a.doPut(apiPrefix+"/skills/"+s.ID, req, &resp); err != nil {
		return profile.Skill{}, err
	}
	return skillFromResponse(resp), nil
}

func (a *apiSkillOperator) Update(s profile.Skill) (profile.Skill, error) {
	req := skillToUpdateRequest(s)
	var resp dto.SkillResponse
	if err := a.doPut(apiPrefix+"/skills/"+s.ID, req, &resp); err != nil {
		return profile.Skill{}, err
	}
	return skillFromResponse(resp), nil
}

func (a *apiSkillOperator) Delete(id string) error {
	return a.doDelete(apiPrefix + "/skills/" + id)
}

func (a *apiSkillOperator) ImportFromFile(filePath string, meta *profile.SkillMeta, force bool) (profile.Skill, bool, error) {
	result, err := a.ImportAutoFromFile(filePath, meta, force)
	if err != nil {
		return profile.Skill{}, false, err
	}
	if result.NeedsMeta {
		return profile.Skill{}, true, nil
	}
	if result.Skill == nil {
		return profile.Skill{}, false, fmt.Errorf("import produced multiple skills; use automatic import handling")
	}
	return *result.Skill, false, nil
}

func (a *apiSkillOperator) ImportAutoFromFile(filePath string, meta *profile.SkillMeta, force bool) (*profile.AutoImportResult, error) {
	var buf bytes.Buffer
	writer := multipart.NewWriter(&buf)

	fileField, err := writer.CreateFormFile("file", filepath.Base(filePath))
	if err != nil {
		return nil, fmt.Errorf("create form file: %w", err)
	}
	f, err := os.Open(filePath)
	if err != nil {
		return nil, fmt.Errorf("open file: %w", err)
	}
	defer f.Close()
	if _, err := io.Copy(fileField, f); err != nil {
		return nil, fmt.Errorf("copy file content: %w", err)
	}

	writeSkillMetaFields(writer, meta, force)
	writer.Close()

	req, err := http.NewRequest("POST", a.baseURL+apiPrefix+"/skills/import", &buf)
	if err != nil {
		return nil, err
	}
	req.Header.Set("X-Api-Key", a.apiKey)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	return a.handleSkillAutoImportResponse(req)
}

func (a *apiSkillOperator) ImportFromURL(rawURL string, meta *profile.SkillMeta, force bool) (profile.Skill, bool, error) {
	result, err := a.ImportAutoFromURL(rawURL, meta, force)
	if err != nil {
		return profile.Skill{}, false, err
	}
	if result.NeedsMeta {
		return profile.Skill{}, true, nil
	}
	if result.Skill == nil {
		return profile.Skill{}, false, fmt.Errorf("import produced multiple skills; use automatic import handling")
	}
	return *result.Skill, false, nil
}

func (a *apiSkillOperator) ImportAutoFromURL(rawURL string, meta *profile.SkillMeta, force bool) (*profile.AutoImportResult, error) {
	var buf bytes.Buffer
	writer := multipart.NewWriter(&buf)

	_ = writer.WriteField("url", rawURL)
	writeSkillMetaFields(writer, meta, force)
	writer.Close()

	req, err := http.NewRequest("POST", a.baseURL+apiPrefix+"/skills/import", &buf)
	if err != nil {
		return nil, err
	}
	req.Header.Set("X-Api-Key", a.apiKey)
	req.Header.Set("Content-Type", writer.FormDataContentType())

	return a.handleSkillAutoImportResponse(req)
}

func writeSkillMetaFields(writer *multipart.Writer, meta *profile.SkillMeta, force bool) {
	if meta != nil {
		if meta.ID != "" {
			_ = writer.WriteField("id", meta.ID)
		}
		if meta.Name != "" {
			_ = writer.WriteField("name", meta.Name)
		}
		if meta.Description != "" {
			_ = writer.WriteField("description", meta.Description)
		}
		if meta.Version != "" {
			_ = writer.WriteField("version", meta.Version)
		}
		if meta.Author != "" {
			_ = writer.WriteField("author", meta.Author)
		}
		if len(meta.Tags) > 0 {
			_ = writer.WriteField("tags", strings.Join(meta.Tags, ","))
		}
	}
	if force {
		_ = writer.WriteField("force", "true")
	}
}

func (a *apiSkillOperator) handleSkillAutoImportResponse(req *http.Request) (*profile.AutoImportResult, error) {
	resp, err := a.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("daemon request failed: %w", err)
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("read response body: %w", err)
	}

	if resp.StatusCode == http.StatusConflict {
		return nil, fmt.Errorf("%w: %s", profile.ErrSkillExists, "")
	}
	if resp.StatusCode == http.StatusUnprocessableEntity {
		return &profile.AutoImportResult{NeedsMeta: true}, nil
	}
	if resp.StatusCode >= 400 {
		return nil, translateError(resp.StatusCode, body)
	}

	var probe struct {
		Imported json.RawMessage `json:"imported"`
	}
	if err := json.Unmarshal(body, &probe); err != nil {
		return nil, fmt.Errorf("parse response: %w", err)
	}
	if len(probe.Imported) > 0 {
		var batchResp dto.BatchImportResponse
		if err := json.Unmarshal(body, &batchResp); err != nil {
			return nil, fmt.Errorf("parse batch response: %w", err)
		}
		result := &profile.BatchImportResponse{
			Imported: make([]profile.BatchImportResult, len(batchResp.Imported)),
			Created:  batchResp.Created,
			Skipped:  batchResp.Skipped,
			Errors:   batchResp.Errors,
		}
		for i, imported := range batchResp.Imported {
			result.Imported[i] = profile.BatchImportResult{
				ID:     imported.ID,
				Name:   imported.Name,
				Status: imported.Status,
			}
		}
		return &profile.AutoImportResult{Batch: result}, nil
	}

	var skillResp dto.SkillResponse
	if err := json.Unmarshal(body, &skillResp); err != nil {
		return nil, fmt.Errorf("parse response: %w", err)
	}
	skill := skillFromResponse(skillResp)
	return &profile.AutoImportResult{Skill: &skill}, nil
}

func skillFromResponse(resp dto.SkillResponse) profile.Skill {
	s := profile.Skill{
		ID:          resp.ID,
		Name:        resp.Name,
		Description: resp.Description,
		Version:     resp.Version,
		Author:      resp.Author,
		Tags:        resp.Tags,
		FileName:    resp.FileName,
	}
	if resp.CreatedAt != nil {
		if t, err := time.Parse(time.RFC3339, *resp.CreatedAt); err == nil {
			s.CreatedAt = &t
		}
	}
	return s
}

func skillToUpdateRequest(s profile.Skill) dto.SkillResponse {
	resp := dto.SkillResponse{
		ID:          s.ID,
		Name:        s.Name,
		Description: s.Description,
		Version:     s.Version,
		Author:      s.Author,
		Tags:        s.Tags,
		FileName:    s.FileName,
	}
	if s.CreatedAt != nil {
		t := s.CreatedAt.UTC().Format(time.RFC3339)
		resp.CreatedAt = &t
	}
	return resp
}

// ===========================================================================
// Helpers
// ===========================================================================

// BaseURL returns the daemon base URL for this API operator.
// This is used by CLI commands that need direct API access.
func (a *apiOperator) BaseURL() string {
	return a.baseURL
}

// APIKey returns the API key used for authentication.
func (a *apiOperator) APIKey() string {
	return a.apiKey
}

// IsAPI returns true, allowing callers to distinguish APIOperator from FileOperator.
func (a *apiOperator) IsAPI() bool {
	return true
}

// AsAPIOperator returns the daemon-API backend when op was created for a
// healthy running daemon. It intentionally exposes the backend only for tests
// and legacy CLI paths that need BaseURL/APIKey; normal command code should use
// the narrow Operator sub-interfaces.
func AsAPIOperator(op Operator) *apiOperator {
	if a, ok := op.(*apiOperator); ok {
		return a
	}
	return nil
}

// IsAPIOperator returns true if the given Operator is an APIOperator.
func IsAPIOperator(op Operator) bool {
	_, ok := op.(*apiOperator)
	return ok
}

// ensure errors package is used
var _ = errors.New
