/*
Copyright 2020 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package egoscale

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

// RunstatusMaintenance is a runstatus maintenance
type RunstatusMaintenance struct {
	Created     *time.Time       `json:"created,omitempty"`
	Description string           `json:"description,omitempty"`
	EndDate     *time.Time       `json:"end_date"`
	Events      []RunstatusEvent `json:"events,omitempty"`
	EventsURL   string           `json:"events_url,omitempty"`
	ID          int              `json:"id,omitempty"`       // missing field
	PageURL     string           `json:"page_url,omitempty"` // fake field
	RealTime    bool             `json:"real_time,omitempty"`
	Services    []string         `json:"services"`
	StartDate   *time.Time       `json:"start_date"`
	Status      string           `json:"status"`
	Title       string           `json:"title"`
	URL         string           `json:"url,omitempty"`
}

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

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

	return false
}

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

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

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

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

// RunstatusMaintenanceList is a list of incident
type RunstatusMaintenanceList struct {
	Next         string                 `json:"next"`
	Previous     string                 `json:"previous"`
	Maintenances []RunstatusMaintenance `json:"results"`
}

// GetRunstatusMaintenance retrieves the details of a specific maintenance.
func (client *Client) GetRunstatusMaintenance(ctx context.Context, maintenance RunstatusMaintenance) (*RunstatusMaintenance, error) {
	if maintenance.URL != "" {
		return client.getRunstatusMaintenance(ctx, maintenance.URL)
	}

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

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

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

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

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

	m := new(RunstatusMaintenance)
	if err := json.Unmarshal(resp, m); err != nil {
		return nil, err
	}
	return m, nil
}

// ListRunstatusMaintenances returns the list of maintenances for the page.
func (client *Client) ListRunstatusMaintenances(ctx context.Context, page RunstatusPage) ([]RunstatusMaintenance, error) {
	if page.MaintenancesURL == "" {
		return nil, fmt.Errorf("empty Maintenances URL for %#v", page)
	}

	results := make([]RunstatusMaintenance, 0)

	var err error
	client.PaginateRunstatusMaintenances(ctx, page, func(maintenance *RunstatusMaintenance, e error) bool {
		if e != nil {
			err = e
			return false
		}

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

	return results, err
}

// PaginateRunstatusMaintenances paginate Maintenances
func (client *Client) PaginateRunstatusMaintenances(ctx context.Context, page RunstatusPage, callback func(*RunstatusMaintenance, error) bool) { // nolint: dupl
	if page.MaintenancesURL == "" {
		callback(nil, fmt.Errorf("empty Maintenances URL for %#v", page))
		return
	}

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

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

		for i := range ms.Maintenances {
			if err := ms.Maintenances[i].FakeID(); err != nil {
				log.Printf("bad fake ID for %#v, %s", ms.Maintenances[i], err)
			}
			if cont := callback(&ms.Maintenances[i], nil); !cont {
				return
			}
		}

		maintenancesURL = ms.Next
	}
}

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

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

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

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

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

	return m, nil
}

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

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