package onepassword

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os/exec"
	"strings"
)

// Client is the 1Password client
type Client struct {
	Domain    string
	Email     string
	Password  string
	SecretKey string
	Session   string
}

// Secret contains the credentials from a 1Password secret
type Secret struct {
	ID         string `json:"id"`
	Title      string `json:"title"`
	Username   string `json:"username"`
	Password   string `json:"password"`
	SecretText string `json:"secretText"`
}

// New authenticates with the provided values and returns a new 1Password client
func New(domain string, email string, password string, secretKey string) (*Client, error) {
	client := &Client{
		Domain:    domain,
		Email:     email,
		Password:  password,
		SecretKey: secretKey,
	}
	if err := client.authenticate(); err != nil {
		return nil, err
	}
	return client, nil
}

func (op *Client) authenticate() error {
	cmd := exec.Command("op", "signin", op.Domain, op.Email, op.SecretKey, "--output=raw")
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return fmt.Errorf("Cannot attach to stdin: %s", err)
	}
	go func() {
		defer stdin.Close()
		if _, err := io.WriteString(stdin, fmt.Sprintf("%s\n", op.Password)); err != nil {
			log.Println("[Error]", err)
		}
	}()
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("Cannot signin: %s\n%s", err, output)
	}
	op.Session = strings.Trim(string(output), "\n")
	return nil
}

func (op Client) runCmd(args ...string) ([]byte, error) {
	args = append(args, fmt.Sprintf("--session=%s", op.Session))
	cmd := exec.Command("op", args...)
	res, err := cmd.CombinedOutput()
	if err != nil {
		err = fmt.Errorf("error calling 1Password: %s\n%s", err, res)
	}
	return res, err
}

// GetSecret returns the values from the secret stored in 1Password with a UUID matching the secretID
func (op *Client) GetSecret(vault, secretID string) (*Secret, error) {
	res, err := op.runCmd("get", "item", secretID, fmt.Sprintf("--vault=%s", vault))
	if err != nil {
		return nil, err
	}
	item := response{}
	if err := json.Unmarshal(res, &item); err != nil {
		return nil, err
	}

	secret := &Secret{
		ID:         item.UUID,
		Title:      item.Overview.Title,
		Username:   "",
		Password:   "",
		SecretText: "",
	}

	if len(item.Details.Fields) > 0 {
		for _, field := range item.Details.Fields {
			switch field.Name {
			case "username":
				secret.Username = field.Value
			case "password":
				secret.Password = field.Value
			}
		}
	}

	if item.Details.Password != nil && *item.Details.Password != "" {
		secret.Password = *item.Details.Password
	}

	if item.Details.Notes != "" {
		secret.SecretText = item.Details.Notes
	}

	return secret, nil
}