package task

import (
	"context"
	"fmt"
	"strconv"
	"time"

	larktask "github.com/larksuite/oapi-sdk-go/v3/service/task/v2"
)

type CreateTaskOriginParam struct {
	Name  string
	URL   string
	Title string
}

type TaskMemberParam struct {
	ID   string
	Type string
	Role string // assignee, follower
}

type CreateTaskTaskListInfo struct {
	TaskListGUID string
	SectionGUID  string
}

type CreateTaskParam struct {
	Title       string
	Description string
	Origin      *CreateTaskOriginParam
	Members     []TaskMemberParam
	TaskList    []CreateTaskTaskListInfo
	StartAt     uint64
	DueAt       *uint64
	Extra       string
	ParentID    string
}

type UpdateTaskParam struct {
	Title       *string
	Description *string
	StartAt     *uint64
	DueAt       *uint64
	Extra       *string
}

func (c *client) CreateTask(ctx context.Context, param CreateTaskParam) (*string /*task id*/, error) {
	req := larktask.NewCreateTaskReqBuilder().UserIdType("user_id").
		InputTask(buildCreateTaskInput(param)).
		Build()

	resp, err := c.larkCli.Task.V2.Task.Create(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("create task failed: %w", err)
	}
	if !resp.Success() {
		return nil, fmt.Errorf("create task failed: %s", resp.CodeError.String())
	}
	return resp.Data.Task.Guid, nil
}

func (c *client) GetTask(ctx context.Context, id string) (*larktask.Task, error) {
	req := larktask.NewGetTaskReqBuilder().
		UserIdType("user_id").
		TaskGuid(id).
		Build()
	resp, err := c.larkCli.Task.V2.Task.Get(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("get task failed: %w", err)
	}
	if !resp.Success() {
		return nil, fmt.Errorf("get task failed: %s", resp.CodeError.String())
	}
	return resp.Data.Task, nil
}

func (c *client) UpdateTask(ctx context.Context, id string, param UpdateTaskParam) error {
	input, fields := buildUpdateTaskInput(param)
	if len(fields) == 0 {
		return nil
	}

	req := larktask.NewPatchTaskReqBuilder().
		UserIdType("user_id").
		TaskGuid(id).
		Body(larktask.NewPatchTaskReqBodyBuilder().
			Task(input).
			UpdateFields(fields).
			Build()).
		Build()
	resp, err := c.larkCli.Task.V2.Task.Patch(ctx, req)
	if err != nil {
		return fmt.Errorf("update task failed: %w", err)
	}
	if !resp.Success() {
		return fmt.Errorf("update task failed: %s", resp.CodeError.String())
	}
	return nil
}

func (c *client) AddTaskMembers(ctx context.Context, id string, members []TaskMemberParam) error {
	req := larktask.NewAddMembersTaskReqBuilder().
		UserIdType("user_id").
		TaskGuid(id).
		Body(larktask.NewAddMembersTaskReqBodyBuilder().
			Members(buildTaskMembers(members)).
			Build()).
		Build()
	resp, err := c.larkCli.Task.V2.Task.AddMembers(ctx, req)
	if err != nil {
		return fmt.Errorf("add task members failed: %w", err)
	}
	if !resp.Success() {
		return fmt.Errorf("add task members failed: %s", resp.CodeError.String())
	}
	return nil
}

func (c *client) RemoveTaskMembers(ctx context.Context, id string, members []TaskMemberParam) error {
	req := larktask.NewRemoveMembersTaskReqBuilder().
		UserIdType("user_id").
		TaskGuid(id).
		Body(larktask.NewRemoveMembersTaskReqBodyBuilder().
			Members(buildTaskMembers(members)).
			Build()).
		Build()
	resp, err := c.larkCli.Task.V2.Task.RemoveMembers(ctx, req)
	if err != nil {
		return fmt.Errorf("remove task members failed: %w", err)
	}
	if !resp.Success() {
		return fmt.Errorf("remove task members failed: %s", resp.CodeError.String())
	}
	return nil
}

func (c *client) CompleteTask(ctx context.Context, id string) error {
	req := larktask.NewPatchTaskReqBuilder().
		UserIdType("user_id").
		TaskGuid(id).
		Body(larktask.NewPatchTaskReqBodyBuilder().
			Task(larktask.NewInputTaskBuilder().
				CompletedAt(strconv.FormatInt(time.Now().UnixMilli(), 10)).
				Build()).
			UpdateFields([]string{"completed_at"}).
			Build()).
		Build()
	resp, err := c.larkCli.Task.V2.Task.Patch(ctx, req)

	if err != nil {
		return fmt.Errorf("complete task failed: %w", err)
	}

	if !resp.Success() {
		fmt.Println(resp.Code, resp.Msg, resp.RequestId())
		return fmt.Errorf("complete task failed: %s", resp.CodeError.String())
	}
	return nil
}

func (c *client) DeleteTask(ctx context.Context, id string) error {
	req := larktask.NewDeleteTaskReqBuilder().TaskGuid(id).Build()
	resp, err := c.larkCli.Task.V2.Task.Delete(ctx, req)

	if err != nil {
		return fmt.Errorf("delete task failed: %w", err)
	}

	if !resp.Success() {
		fmt.Println(resp.Code, resp.Msg, resp.RequestId())
		return fmt.Errorf("delete task failed: %s", resp.CodeError.String())
	}
	return nil
}

func (c *client) CreateSubTask(ctx context.Context, id string, param CreateTaskParam) error {
	req := larktask.NewCreateTaskSubtaskReqBuilder().
		UserIdType("user_id").
		TaskGuid(id).
		InputTask(buildCreateTaskInput(param)).
		Build()
	resp, err := c.larkCli.Task.V2.TaskSubtask.Create(ctx, req)
	if err != nil {
		return fmt.Errorf("create sub task failed: %w", err)
	}
	if !resp.Success() {
		return fmt.Errorf("create sub task failed: %s", resp.CodeError.String())
	}
	return nil
}

func buildCreateTaskInput(param CreateTaskParam) *larktask.InputTask {
	taskBuilder := larktask.NewInputTaskBuilder().
		Summary(param.Title).
		Description(param.Description)

	if param.Origin != nil {
		originBuilder := larktask.NewOriginBuilder()
		hasOrigin := false
		if param.Origin.Name != "" {
			originBuilder.PlatformI18nName(larktask.NewI18nTextBuilder().
				ZhCn(param.Origin.Name).
				EnUs(param.Origin.Name).
				Build())
			hasOrigin = true
		}
		if param.Origin.URL != "" || param.Origin.Title != "" {
			hrefBuilder := larktask.NewHrefBuilder()
			if param.Origin.URL != "" {
				hrefBuilder.Url(param.Origin.URL)
			}
			if param.Origin.Title != "" {
				hrefBuilder.Title(param.Origin.Title)
			}
			originBuilder.Href(hrefBuilder.Build())
			hasOrigin = true
		}
		if hasOrigin {
			taskBuilder.Origin(originBuilder.Build())
		}
	}

	if len(param.Members) > 0 {
		taskBuilder.Members(buildTaskMembers(param.Members))
	}

	if len(param.TaskList) > 0 {
		taskLists := make([]*larktask.TaskInTasklistInfo, 0, len(param.TaskList))
		for _, taskList := range param.TaskList {
			if taskList.TaskListGUID == "" && taskList.SectionGUID == "" {
				continue
			}
			taskListBuilder := larktask.NewTaskInTasklistInfoBuilder()
			if taskList.TaskListGUID != "" {
				taskListBuilder.TasklistGuid(taskList.TaskListGUID)
			}
			if taskList.SectionGUID != "" {
				taskListBuilder.SectionGuid(taskList.SectionGUID)
			}
			taskLists = append(taskLists, taskListBuilder.Build())
		}
		if len(taskLists) > 0 {
			taskBuilder.Tasklists(taskLists)
		}
	}

	if param.StartAt > 0 {
		taskBuilder.Start(larktask.NewStartBuilder().
			Timestamp(unixSecondsToMillisString(param.StartAt)).
			IsAllDay(false).
			Build())
	}

	if param.DueAt != nil && *param.DueAt > 0 {
		taskBuilder.Due(larktask.NewDueBuilder().
			Timestamp(unixSecondsToMillisString(*param.DueAt)).
			IsAllDay(false).
			Build())
	}

	if param.Extra != "" {
		taskBuilder.Extra(param.Extra)
	}

	return taskBuilder.Build()
}

func buildTaskMembers(members []TaskMemberParam) []*larktask.Member {
	larkMembers := make([]*larktask.Member, 0, len(members))
	for _, member := range members {
		larkMembers = append(larkMembers, buildTaskMember(member))
	}
	return larkMembers
}

func buildTaskMember(member TaskMemberParam) *larktask.Member {
	memberBuilder := larktask.NewMemberBuilder().Type("user")
	if member.ID != "" {
		memberBuilder.Id(member.ID)
	}
	if member.Type != "" {
		memberBuilder.Type(member.Type)
	}
	if member.Role != "" {
		memberBuilder.Role(member.Role)
	}
	return memberBuilder.Build()
}

func buildUpdateTaskInput(param UpdateTaskParam) (*larktask.InputTask, []string) {
	taskBuilder := larktask.NewInputTaskBuilder()
	fields := make([]string, 0, 5)

	if param.Title != nil {
		taskBuilder.Summary(*param.Title)
		fields = append(fields, "summary")
	}

	if param.Description != nil {
		taskBuilder.Description(*param.Description)
		fields = append(fields, "description")
	}

	if param.StartAt != nil {
		taskBuilder.Start(larktask.NewStartBuilder().
			Timestamp(unixSecondsToMillisString(*param.StartAt)).
			IsAllDay(false).
			Build())
		fields = append(fields, "start")
	}

	if param.DueAt != nil {
		taskBuilder.Due(larktask.NewDueBuilder().
			Timestamp(unixSecondsToMillisString(*param.DueAt)).
			IsAllDay(false).
			Build())
		fields = append(fields, "due")
	}

	if param.Extra != nil {
		taskBuilder.Extra(*param.Extra)
		fields = append(fields, "extra")
	}

	return taskBuilder.Build(), fields
}

func unixSecondsToMillisString(ts uint64) string {
	return strconv.FormatUint(ts*1000, 10)
}
