package linodego

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

	"github.com/linode/linodego/internal/parseabletime"
)

// InstanceBackupsResponse response struct for backup snapshot
type InstanceBackupsResponse struct {
	Automatic []*InstanceSnapshot             `json:"automatic"`
	Snapshot  *InstanceBackupSnapshotResponse `json:"snapshot"`
}

// InstanceBackupSnapshotResponse fields are those representing Instance Backup Snapshots
type InstanceBackupSnapshotResponse struct {
	Current    *InstanceSnapshot `json:"current"`
	InProgress *InstanceSnapshot `json:"in_progress"`
}

// RestoreInstanceOptions fields are those accepted by InstanceRestore
type RestoreInstanceOptions struct {
	LinodeID  int  `json:"linode_id"`
	Overwrite bool `json:"overwrite"`
}

// InstanceSnapshot represents a linode backup snapshot
type InstanceSnapshot struct {
	ID       int                     `json:"id"`
	Label    string                  `json:"label"`
	Status   InstanceSnapshotStatus  `json:"status"`
	Type     string                  `json:"type"`
	Created  *time.Time              `json:"-"`
	Updated  *time.Time              `json:"-"`
	Finished *time.Time              `json:"-"`
	Configs  []string                `json:"configs"`
	Disks    []*InstanceSnapshotDisk `json:"disks"`
}

// InstanceSnapshotDisk fields represent the source disk of a Snapshot
type InstanceSnapshotDisk struct {
	Label      string `json:"label"`
	Size       int    `json:"size"`
	Filesystem string `json:"filesystem"`
}

// InstanceSnapshotStatus constants start with Snapshot and include Linode API Instance Backup Snapshot status values
type InstanceSnapshotStatus string

// InstanceSnapshotStatus constants reflect the current status of an Instance Snapshot
var (
	SnapshotPaused              InstanceSnapshotStatus = "paused"
	SnapshotPending             InstanceSnapshotStatus = "pending"
	SnapshotRunning             InstanceSnapshotStatus = "running"
	SnapshotNeedsPostProcessing InstanceSnapshotStatus = "needsPostProcessing"
	SnapshotSuccessful          InstanceSnapshotStatus = "successful"
	SnapshotFailed              InstanceSnapshotStatus = "failed"
	SnapshotUserAborted         InstanceSnapshotStatus = "userAborted"
)

// UnmarshalJSON implements the json.Unmarshaler interface
func (i *InstanceSnapshot) UnmarshalJSON(b []byte) error {
	type Mask InstanceSnapshot

	p := struct {
		*Mask
		Created  *parseabletime.ParseableTime `json:"created"`
		Updated  *parseabletime.ParseableTime `json:"updated"`
		Finished *parseabletime.ParseableTime `json:"finished"`
	}{
		Mask: (*Mask)(i),
	}

	if err := json.Unmarshal(b, &p); err != nil {
		return err
	}

	i.Created = (*time.Time)(p.Created)
	i.Updated = (*time.Time)(p.Updated)
	i.Finished = (*time.Time)(p.Finished)

	return nil
}

// GetInstanceSnapshot gets the snapshot with the provided ID
func (c *Client) GetInstanceSnapshot(ctx context.Context, linodeID int, snapshotID int) (*InstanceSnapshot, error) {
	e, err := c.InstanceSnapshots.endpointWithID(linodeID)
	if err != nil {
		return nil, err
	}
	e = fmt.Sprintf("%s/%d", e, snapshotID)
	r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceSnapshot{}).Get(e))

	if err != nil {
		return nil, err
	}
	return r.Result().(*InstanceSnapshot), nil
}

// CreateInstanceSnapshot Creates or Replaces the snapshot Backup of a Linode. If a previous snapshot exists for this Linode, it will be deleted.
func (c *Client) CreateInstanceSnapshot(ctx context.Context, linodeID int, label string) (*InstanceSnapshot, error) {
	o, err := json.Marshal(map[string]string{"label": label})
	if err != nil {
		return nil, err
	}
	body := string(o)
	e, err := c.InstanceSnapshots.endpointWithID(linodeID)

	if err != nil {
		return nil, err
	}

	r, err := coupleAPIErrors(c.R(ctx).
		SetBody(body).
		SetResult(&InstanceSnapshot{}).
		Post(e))

	if err != nil {
		return nil, err
	}

	return r.Result().(*InstanceSnapshot), nil
}

// GetInstanceBackups gets the Instance's available Backups.
// This is not called ListInstanceBackups because a single object is returned, matching the API response.
func (c *Client) GetInstanceBackups(ctx context.Context, linodeID int) (*InstanceBackupsResponse, error) {
	e, err := c.InstanceSnapshots.endpointWithID(linodeID)
	if err != nil {
		return nil, err
	}
	r, err := coupleAPIErrors(c.R(ctx).
		SetResult(&InstanceBackupsResponse{}).
		Get(e))

	if err != nil {
		return nil, err
	}
	return r.Result().(*InstanceBackupsResponse), nil
}

// EnableInstanceBackups Enables backups for the specified Linode.
func (c *Client) EnableInstanceBackups(ctx context.Context, linodeID int) error {
	e, err := c.InstanceSnapshots.endpointWithID(linodeID)
	if err != nil {
		return err
	}
	e = fmt.Sprintf("%s/enable", e)

	_, err = coupleAPIErrors(c.R(ctx).Post(e))
	return err
}

// CancelInstanceBackups Cancels backups for the specified Linode.
func (c *Client) CancelInstanceBackups(ctx context.Context, linodeID int) error {
	e, err := c.InstanceSnapshots.endpointWithID(linodeID)
	if err != nil {
		return err
	}
	e = fmt.Sprintf("%s/cancel", e)

	_, err = coupleAPIErrors(c.R(ctx).Post(e))
	return err
}

// RestoreInstanceBackup Restores a Linode's Backup to the specified Linode.
func (c *Client) RestoreInstanceBackup(ctx context.Context, linodeID int, backupID int, opts RestoreInstanceOptions) error {
	o, err := json.Marshal(opts)
	if err != nil {
		return NewError(err)
	}
	body := string(o)
	e, err := c.InstanceSnapshots.endpointWithID(linodeID)
	if err != nil {
		return err
	}
	e = fmt.Sprintf("%s/%d/restore", e, backupID)

	_, err = coupleAPIErrors(c.R(ctx).SetBody(body).Post(e))

	return err
}
