package codecov

import (
	"encoding/json"
	"time"
)

// CoverageReport generated by Codecov.
type CoverageReport struct {
	Owner  *Owner  `json:"owner"`
	Repo   *Repo   `json:"repo"`
	Meta   *Meta   `json:"meta"`
	Commit *Commit `json:"commit"`
}

// Owner describes the GitHub organization.
type Owner struct {
	Username      string    `json:"username"`
	IntegrationID string    `json:"integration_id"`
	Name          string    `json:"name"`
	Service       string    `json:"service"`
	UpdateStamp   Timestamp `json:"updatestamp"`
	ServiceID     string    `json:"service_id"`
}

// Repo describes the GitHub repository.
type Repo struct {
	UsingIntegration string    `json:"using_integration"`
	Name             string    `json:"name"`
	Language         string    `json:"language"`
	Deleted          bool      `json:"deleted"`
	Activated        bool      `json:"activated"`
	Private          bool      `json:"private"`
	UpdateStamp      Timestamp `json:"updatestamp"`
	Branch           string    `json:"branch"`
	Active           bool      `json:"active"`
	ServiceID        string    `json:"service_id"`
	ImageToken       string    `json:"image_token"`
}

// Meta contains fields about the HTTP request status and for pagination.
type Meta struct {
	Status int `json:"status"`
}

// Commit describes the commit that was analyzed.
type Commit struct {
	PullID       string    `json:"pullid"`
	Archived     bool      `json:"archived"`
	Version      int       `json:"version"`
	Parent       string    `json:"parent"`
	Author       *Author   `json:"author"`
	Deleted      bool      `json:"deleted"`
	Timestamp    Timestamp `json:"timestamp"`
	ParentTotals *Totals   `json:"parent_totals"`
	State        string    `json:"state"`
	Totals       *Totals   `json:"totals"`
	CommitID     string    `json:"commitid"`
	Notified     bool      `json:"notified"`
	CIPassed     bool      `json:"ci_passed"`
	UpdateStamp  Timestamp `json:"updatestamp"`
	Branch       string    `json:"branch"`
	Report       string    `json:"report"`
	Message      string    `json:"message"`
	Merged       bool      `json:"merged"`
}

// Author describes the author of the commit.
type Author struct {
	Username  string `json:"username"`
	ServiceID string `json:"service_id"`
	Email     string `json:"email"`
	Service   string `json:"service"`
	Name      string `json:"name"`
}

// Totals are the coverage metrics generated by Codecov.
type Totals struct {
	CoverageRatio   float64     `json:"c,string"` // Hits / (Hits + Partials + Misses)
	BranchesCounted int         `json:"b"`
	MethodsCounted  int         `json:"d"`
	FilesCounted    int         `json:"f"`
	HitsCounted     int         `json:"h"` // Code that was fully executed
	MissesCounted   int         `json:"m"` // Code that wasn't executed
	LinesCounted    int         `json:"n"`
	PartialsCounted int         `json:"p"` // Code that wasn't fully executed
	Diff            interface{} `json:"diff"`
	Complexity      int         `json:"C"`
	MessagesCounted int         `json:"M"`
	NamesCounted    int         `json:"N"`
	SessionsCounted int         `json:"s"`
}

// Timestamp format used by Codecov.
type Timestamp struct {
	time.Time
}

// Unmarshal Codecov Coverage JSON into Go structs.
func Unmarshal(data []byte) (*CoverageReport, error) {
	coverageReport := &CoverageReport{}
	err := json.Unmarshal(data, coverageReport)
	if err != nil {
		return nil, err
	}
	return coverageReport, nil
}

// UnmarshalJSON implements the json.Unmarshaler interface.
// The time is expected to be a quoted string in RFC 3339 format.
func (t *Timestamp) UnmarshalJSON(data []byte) error {
	// Ignore null, like in the main JSON package.
	if string(data) == "null" {
		return nil
	}
	// Fractional seconds are handled implicitly by Parse.
	var err error
	t.Time, err = time.Parse(`"2006-01-02 15:04:05.99999-07"`, string(data))
	if err != nil {
		t.Time, err = time.Parse(`"2006-01-02 15:04:05.99999"`, string(data))
	}
	return err
}
