package forwarder

import (
	"context"
	"errors"
	"io"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/agent"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
)

type SkillGateway struct {
	upstream *Handler
}

func NewSkillGateway(dependencies Dependencies) (*SkillGateway, error) {
	upstream, err := NewHandler(dependencies)
	if err != nil {
		return nil, err
	}
	return &SkillGateway{upstream: upstream}, nil
}

func (gateway *SkillGateway) List(
	ctx context.Context,
	principal agent.AgentSkillPrincipal,
	input agent.AgentSkillListInput,
) (dto.SkillListResponse, error) {
	principal, err := normalizeAgentSkillPrincipal(principal)
	if err != nil {
		return dto.SkillListResponse{}, err
	}
	values := url.Values{}
	if value := strings.TrimSpace(input.Query); value != "" {
		values.Set("q", value)
	}
	if input.Page != 0 {
		values.Set("page", strconv.Itoa(input.Page))
	}
	if input.PerPage != 0 {
		values.Set("per_page", strconv.Itoa(input.PerPage))
	}
	if value := strings.TrimSpace(input.TemplateLocale); value != "" {
		values.Set("template_locale", value)
	}
	if value := strings.TrimSpace(input.View); value != "" {
		values.Set("view", value)
	}
	path := "/api/v1/skills"
	if encoded := values.Encode(); encoded != "" {
		path += "?" + encoded
	}
	var result dto.SkillListResponse
	err = gateway.forwardJSON(ctx, JSONRequest{
		Method: http.MethodGet, Path: path,
		WorkspaceID: principal.WorkspaceID, Identity: agentSkillIdentity(principal.Subject),
	}, &result)
	return result, err
}

func (gateway *SkillGateway) Get(
	ctx context.Context,
	principal agent.AgentSkillPrincipal,
	skillID string,
) (dto.Skill, error) {
	principal, err := normalizeAgentSkillPrincipal(principal)
	if err != nil {
		return dto.Skill{}, err
	}
	skillID, err = safeAgentSkillID(skillID)
	if err != nil {
		return dto.Skill{}, err
	}
	var result dto.Skill
	err = gateway.forwardJSON(ctx, JSONRequest{
		Method: http.MethodGet, Path: "/api/v1/skills/" + skillID,
		WorkspaceID: principal.WorkspaceID, Identity: agentSkillIdentity(principal.Subject),
	}, &result)
	return result, err
}

func (gateway *SkillGateway) Create(
	ctx context.Context,
	principal agent.AgentSkillPrincipal,
	input dto.SkillCreateRequest,
) (dto.Skill, error) {
	principal, err := normalizeAgentSkillPrincipal(principal)
	if err != nil {
		return dto.Skill{}, err
	}
	var result dto.Skill
	err = gateway.forwardJSON(ctx, JSONRequest{
		Method: http.MethodPost, Path: "/api/v1/skills",
		WorkspaceID: principal.WorkspaceID, Identity: agentSkillIdentity(principal.Subject),
		Body: input,
	}, &result)
	return result, err
}

func (gateway *SkillGateway) Update(
	ctx context.Context,
	principal agent.AgentSkillPrincipal,
	skillID string,
	input agent.AgentSkillUpdateInput,
) (dto.Skill, error) {
	principal, err := normalizeAgentSkillPrincipal(principal)
	if err != nil {
		return dto.Skill{}, err
	}
	skillID, err = safeAgentSkillID(skillID)
	if err != nil {
		return dto.Skill{}, err
	}
	body := struct {
		Name        *string `json:"name,omitempty"`
		Description *string `json:"description,omitempty"`
	}{Name: input.Name, Description: input.Description}
	var result dto.Skill
	err = gateway.forwardJSON(ctx, JSONRequest{
		Method: http.MethodPatch, Path: "/api/v1/skills/" + skillID,
		WorkspaceID: principal.WorkspaceID, Identity: agentSkillIdentity(principal.Subject),
		Body: body,
	}, &result)
	return result, err
}

func (gateway *SkillGateway) Import(
	ctx context.Context,
	principal agent.AgentSkillPrincipal,
	skillID string,
	contentType string,
	body io.Reader,
) (dto.SkillImportResponse, error) {
	principal, err := normalizeAgentSkillPrincipal(principal)
	if err != nil {
		return dto.SkillImportResponse{}, err
	}
	skillID, err = safeAgentSkillID(skillID)
	if err != nil {
		return dto.SkillImportResponse{}, err
	}
	var result dto.SkillImportResponse
	if gateway == nil || gateway.upstream == nil {
		return result, api.NewHTTPError(http.StatusBadGateway, "Skill API request failed")
	}
	err = gateway.upstream.ForwardMultipart(ctx, MultipartRequest{
		Method: http.MethodPost, Path: "/api/v1/skills/" + skillID + "/imports",
		WorkspaceID: principal.WorkspaceID, Identity: agentSkillIdentity(principal.Subject),
		ContentType: contentType, Body: body,
	}, &result)
	if err != nil {
		return dto.SkillImportResponse{}, mapAgentSkillGatewayError(err)
	}
	return result, nil
}

func (gateway *SkillGateway) Delete(
	ctx context.Context,
	principal agent.AgentSkillPrincipal,
	skillID string,
) error {
	principal, err := normalizeAgentSkillPrincipal(principal)
	if err != nil {
		return err
	}
	skillID, err = safeAgentSkillID(skillID)
	if err != nil {
		return err
	}
	return gateway.forwardJSON(ctx, JSONRequest{
		Method: http.MethodDelete, Path: "/api/v1/skills/" + skillID,
		WorkspaceID:    principal.WorkspaceID,
		Identity:       agentSkillIdentity(principal.Subject),
		ExpectedStatus: http.StatusNoContent,
	}, nil)
}

func (gateway *SkillGateway) forwardJSON(
	ctx context.Context,
	request JSONRequest,
	response any,
) error {
	if gateway == nil || gateway.upstream == nil {
		return api.NewHTTPError(http.StatusBadGateway, "Skill API request failed")
	}
	if err := gateway.upstream.ForwardJSON(ctx, request, response); err != nil {
		return mapAgentSkillGatewayError(err)
	}
	return nil
}

func normalizeAgentSkillPrincipal(
	principal agent.AgentSkillPrincipal,
) (agent.AgentSkillPrincipal, error) {
	principal.WorkspaceID = strings.TrimSpace(principal.WorkspaceID)
	principal.Subject = strings.TrimSpace(principal.Subject)
	if principal.WorkspaceID == "" || principal.Subject == "" {
		return agent.AgentSkillPrincipal{}, api.NewHTTPError(
			http.StatusInternalServerError, "Skill principal is unavailable",
		)
	}
	return principal, nil
}

func safeAgentSkillID(raw string) (string, error) {
	skillID := strings.TrimSpace(raw)
	if skillID == "" || skillID == "." || skillID == ".." || url.PathEscape(skillID) != skillID {
		return "", api.NewHTTPError(http.StatusBadRequest, "skillId is invalid")
	}
	return skillID, nil
}

func agentSkillIdentity(subject string) TrustedIdentity {
	subject = strings.TrimSpace(subject)
	return TrustedIdentity{
		UserID: subject, AgentTeamUserID: subject,
		PreferredUsername: normalizePreferredUsernameCandidate(subject),
	}
}

func mapAgentSkillGatewayError(err error) error {
	var httpError api.HTTPError
	if errors.As(err, &httpError) {
		return err
	}
	var upstream UpstreamHTTPError
	if errors.As(err, &upstream) {
		return api.NewHTTPErrorWithCodeAndCause(
			upstream.StatusCode, "Skill API request failed", upstream.Code, err,
		)
	}
	return api.NewHTTPErrorWithCause(
		http.StatusBadGateway, "Skill API request failed", err,
	)
}

var _ agent.AgentSkillGateway = (*SkillGateway)(nil)
