package egoscale

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/url"
	"path"
	"strconv"
)

// RunstatusService is a runstatus service
type RunstatusService struct {
	ID      int    `json:"id"` // missing field
	Name    string `json:"name"`
	PageURL string `json:"page_url,omitempty"` // fake field
	State   string `json:"state,omitempty"`
	URL     string `json:"url,omitempty"`
}

// FakeID fills up the ID field as it's currently missing
func (service *RunstatusService) FakeID() error {
	if service.ID > 0 {
		return nil
	}

	if service.URL == "" {
		return fmt.Errorf("empty URL for %#v", service)
	}

	u, err := url.Parse(service.URL)
	if err != nil {
		return err
	}

	s := path.Base(u.Path)
	id, err := strconv.Atoi(s)
	if err != nil {
		return err
	}
	service.ID = id
	return nil
}

// Match returns true if the other service has got similarities with itself
func (service RunstatusService) Match(other RunstatusService) bool {
	if other.Name != "" && service.Name == other.Name {
		return true
	}

	if other.ID > 0 && service.ID == other.ID {
		return true
	}

	return false
}

// RunstatusServiceList service list
type RunstatusServiceList struct {
	Next     string             `json:"next"`
	Previous string             `json:"previous"`
	Services []RunstatusService `json:"results"`
}

// DeleteRunstatusService delete runstatus service
func (client *Client) DeleteRunstatusService(ctx context.Context, service RunstatusService) error {
	if service.URL == "" {
		return fmt.Errorf("empty URL for %#v", service)
	}

	_, err := client.runstatusRequest(ctx, service.URL, nil, "DELETE")
	return err
}

// CreateRunstatusService create runstatus service
func (client *Client) CreateRunstatusService(ctx context.Context, service RunstatusService) (*RunstatusService, error) {
	if service.PageURL == "" {
		return nil, fmt.Errorf("empty Page URL for %#v", service)
	}

	page, err := client.GetRunstatusPage(ctx, RunstatusPage{URL: service.PageURL})
	if err != nil {
		return nil, err
	}

	resp, err := client.runstatusRequest(ctx, page.ServicesURL, service, "POST")
	if err != nil {
		return nil, err
	}

	s := &RunstatusService{}
	if err := json.Unmarshal(resp, s); err != nil {
		return nil, err
	}

	return s, nil
}

// GetRunstatusService displays service detail.
func (client *Client) GetRunstatusService(ctx context.Context, service RunstatusService) (*RunstatusService, error) {
	if service.URL != "" {
		return client.getRunstatusService(ctx, service.URL)
	}

	if service.PageURL == "" {
		return nil, fmt.Errorf("empty Page URL in %#v", service)
	}

	page, err := client.getRunstatusPage(ctx, service.PageURL)
	if err != nil {
		return nil, err
	}

	for i := range page.Services {
		s := &page.Services[i]
		if s.Match(service) {
			if err := s.FakeID(); err != nil {
				log.Printf("bad fake ID for %#v, %s", s, err)
			}
			return s, nil
		}
	}

	return nil, errors.New("service not found")
}

func (client *Client) getRunstatusService(ctx context.Context, serviceURL string) (*RunstatusService, error) {
	resp, err := client.runstatusRequest(ctx, serviceURL, nil, "GET")
	if err != nil {
		return nil, err
	}

	s := &RunstatusService{}
	if err := json.Unmarshal(resp, &s); err != nil {
		return nil, err
	}

	if err := s.FakeID(); err != nil {
		log.Printf("bad fake ID for %#v, %s", s, err)
	}

	return s, nil
}

// ListRunstatusServices displays the list of services.
func (client *Client) ListRunstatusServices(ctx context.Context, page RunstatusPage) ([]RunstatusService, error) {
	if page.ServicesURL == "" {
		return nil, fmt.Errorf("empty Services URL for %#v", page)
	}

	results := make([]RunstatusService, 0)

	var err error
	client.PaginateRunstatusServices(ctx, page, func(service *RunstatusService, e error) bool {
		if e != nil {
			err = e
			return false
		}

		results = append(results, *service)
		return true
	})

	return results, err
}

// PaginateRunstatusServices paginates Services
func (client *Client) PaginateRunstatusServices(ctx context.Context, page RunstatusPage, callback func(*RunstatusService, error) bool) { // nolint: dupl
	if page.ServicesURL == "" {
		callback(nil, fmt.Errorf("empty Services URL for %#v", page))
		return
	}

	servicesURL := page.ServicesURL
	for servicesURL != "" {
		resp, err := client.runstatusRequest(ctx, servicesURL, nil, "GET")
		if err != nil {
			callback(nil, err)
			return
		}

		var ss *RunstatusServiceList
		if err := json.Unmarshal(resp, &ss); err != nil {
			callback(nil, err)
			return
		}

		for i := range ss.Services {
			if err := ss.Services[i].FakeID(); err != nil {
				log.Printf("bad fake ID for %#v, %s", ss.Services[i], err)
			}

			if cont := callback(&ss.Services[i], nil); !cont {
				return
			}
		}

		servicesURL = ss.Next
	}
}
