// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package lunatask

import (
	"context"
	"encoding/json"
	"fmt"
	"maps"
	"time"
)

// Person is a contact in Lunatask's relationship tracker.
// FirstName and LastName are encrypted client-side and will be null when read.
type Person struct {
	ID                   string                `json:"id"`
	RelationshipStrength *RelationshipStrength `json:"relationship_strength"`
	Sources              []Source              `json:"sources"`
	CreatedAt            time.Time             `json:"created_at"`
	UpdatedAt            time.Time             `json:"updated_at"`
}

// createPersonRequest defines a new person for JSON serialization.
//
// CustomFields allows setting arbitrary custom fields. Lunatask supports
// "email", "birthday", and "phone" out of the box; other fields must first be
// defined in the app or [ErrUnprocessableEntity] is returned. These are
// flattened to top-level JSON fields when marshaled.
type createPersonRequest struct {
	FirstName            *string               `json:"first_name,omitempty"`
	LastName             *string               `json:"last_name,omitempty"`
	RelationshipStrength *RelationshipStrength `json:"relationship_strength,omitempty"`
	Source               *string               `json:"source,omitempty"`
	SourceID             *string               `json:"source_id,omitempty"`
	CustomFields         map[string]any        `json:"-"`
}

// MarshalJSON flattens CustomFields to top-level JSON fields.
func (r createPersonRequest) MarshalJSON() ([]byte, error) {
	// Alias to avoid infinite recursion
	type plain createPersonRequest

	base, err := json.Marshal(plain(r))
	if err != nil {
		return nil, fmt.Errorf("marshaling person request: %w", err)
	}

	if len(r.CustomFields) == 0 {
		return base, nil
	}

	// Merge custom fields into the base object
	var merged map[string]any
	if err := json.Unmarshal(base, &merged); err != nil {
		return nil, fmt.Errorf("unmarshaling person request for merge: %w", err)
	}

	maps.Copy(merged, r.CustomFields)

	data, err := json.Marshal(merged)
	if err != nil {
		return nil, fmt.Errorf("marshaling merged person request: %w", err)
	}

	return data, nil
}

// personResponse wraps a single person from the API.
type personResponse struct {
	Person Person `json:"person"`
}

// peopleResponse wraps a list of people from the API.
type peopleResponse struct {
	People []Person `json:"people"`
}

// ListPeopleOptions filters people by source integration.
type ListPeopleOptions struct {
	Source   *string
	SourceID *string
}

// GetSource implements [SourceFilter].
func (o *ListPeopleOptions) GetSource() *string { return o.Source }

// GetSourceID implements [SourceFilter].
func (o *ListPeopleOptions) GetSourceID() *string { return o.SourceID }

// ListPeople returns all people, optionally filtered. Pass nil for all.
func (c *Client) ListPeople(ctx context.Context, opts *ListPeopleOptions) ([]Person, error) {
	var filter SourceFilter
	if opts != nil {
		filter = opts
	}

	return list(ctx, c, "/people", filter, func(r peopleResponse) []Person { return r.People })
}

// GetPerson fetches a person by ID. Name fields will be null (E2EE).
func (c *Client) GetPerson(ctx context.Context, personID string) (*Person, error) {
	return get(ctx, c, "/people", personID, "person", func(r personResponse) Person { return r.Person })
}

// DeletePerson removes a person and returns their final state.
func (c *Client) DeletePerson(ctx context.Context, personID string) (*Person, error) {
	return del(ctx, c, "/people", personID, "person", func(r personResponse) Person { return r.Person })
}

// PersonBuilder constructs and creates a person via method chaining.
// Name fields are encrypted client-side; the API accepts them on create but
// returns null on read.
//
//	person, err := client.NewPerson("Ada", "Lovelace").
//		WithRelationshipStrength(lunatask.RelationshipCloseFriend).
//		Create(ctx)
type PersonBuilder struct {
	client *Client
	req    createPersonRequest
}

// NewPerson starts building a person entry with the given name.
func (c *Client) NewPerson(firstName, lastName string) *PersonBuilder {
	return &PersonBuilder{
		client: c,
		req:    createPersonRequest{FirstName: &firstName, LastName: &lastName},
	}
}

// WithRelationshipStrength categorizes the closeness of the relationship.
// Use one of the Relationship* constants (e.g., [RelationshipCloseFriend]).
func (b *PersonBuilder) WithRelationshipStrength(strength RelationshipStrength) *PersonBuilder {
	b.req.RelationshipStrength = &strength

	return b
}

// FromSource tags the person with a free-form origin identifier, useful for
// tracking entries created by scripts or external integrations.
func (b *PersonBuilder) FromSource(source, sourceID string) *PersonBuilder {
	b.req.Source = &source
	b.req.SourceID = &sourceID

	return b
}

// WithCustomField sets an arbitrary custom field. Lunatask supports "email",
// "birthday", and "phone" out of the box; other fields must first be defined
// in the app or [ErrUnprocessableEntity] is returned.
func (b *PersonBuilder) WithCustomField(key string, value any) *PersonBuilder {
	if b.req.CustomFields == nil {
		b.req.CustomFields = make(map[string]any)
	}

	b.req.CustomFields[key] = value

	return b
}

// Create sends the person to Lunatask.
// Returns (nil, nil) if a duplicate exists with matching source/source_id.
func (b *PersonBuilder) Create(ctx context.Context) (*Person, error) {
	return create(ctx, b.client, "/people", b.req, func(r personResponse) Person { return r.Person })
}
