/*
 * Copyright 2018-2019 The NATS 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 jwt

import (
	"encoding/json"
	"fmt"
	"net"
	"strings"
	"time"
)

// ExportType defines the type of import/export.
type ExportType int

const (
	// Unknown is used if we don't know the type
	Unknown ExportType = iota
	// Stream defines the type field value for a stream "stream"
	Stream
	// Service defines the type field value for a service "service"
	Service
)

func (t ExportType) String() string {
	switch t {
	case Stream:
		return "stream"
	case Service:
		return "service"
	}
	return "unknown"
}

// MarshalJSON marshals the enum as a quoted json string
func (t *ExportType) MarshalJSON() ([]byte, error) {
	switch *t {
	case Stream:
		return []byte("\"stream\""), nil
	case Service:
		return []byte("\"service\""), nil
	}
	return nil, fmt.Errorf("unknown export type")
}

// UnmarshalJSON unmashals a quoted json string to the enum value
func (t *ExportType) UnmarshalJSON(b []byte) error {
	var j string
	err := json.Unmarshal(b, &j)
	if err != nil {
		return err
	}
	switch j {
	case "stream":
		*t = Stream
		return nil
	case "service":
		*t = Service
		return nil
	}
	return fmt.Errorf("unknown export type")
}

// Subject is a string that represents a NATS subject
type Subject string

// Validate checks that a subject string is valid, ie not empty and without spaces
func (s Subject) Validate(vr *ValidationResults) {
	v := string(s)
	if v == "" {
		vr.AddError("subject cannot be empty")
	}
	if strings.Contains(v, " ") {
		vr.AddError("subject %q cannot have spaces", v)
	}
}

// HasWildCards is used to check if a subject contains a > or *
func (s Subject) HasWildCards() bool {
	v := string(s)
	return strings.HasSuffix(v, ".>") ||
		strings.Contains(v, ".*.") ||
		strings.HasSuffix(v, ".*") ||
		strings.HasPrefix(v, "*.") ||
		v == "*" ||
		v == ">"
}

// IsContainedIn does a simple test to see if the subject is contained in another subject
func (s Subject) IsContainedIn(other Subject) bool {
	otherArray := strings.Split(string(other), ".")
	myArray := strings.Split(string(s), ".")

	if len(myArray) > len(otherArray) && otherArray[len(otherArray)-1] != ">" {
		return false
	}

	if len(myArray) < len(otherArray) {
		return false
	}

	for ind, tok := range otherArray {
		myTok := myArray[ind]

		if ind == len(otherArray)-1 && tok == ">" {
			return true
		}

		if tok != myTok && tok != "*" {
			return false
		}
	}

	return true
}

// NamedSubject is the combination of a subject and a name for it
type NamedSubject struct {
	Name    string  `json:"name,omitempty"`
	Subject Subject `json:"subject,omitempty"`
}

// Validate checks the subject
func (ns *NamedSubject) Validate(vr *ValidationResults) {
	ns.Subject.Validate(vr)
}

// TimeRange is used to represent a start and end time
type TimeRange struct {
	Start string `json:"start,omitempty"`
	End   string `json:"end,omitempty"`
}

// Validate checks the values in a time range struct
func (tr *TimeRange) Validate(vr *ValidationResults) {
	format := "15:04:05"

	if tr.Start == "" {
		vr.AddError("time ranges start must contain a start")
	} else {
		_, err := time.Parse(format, tr.Start)
		if err != nil {
			vr.AddError("start in time range is invalid %q", tr.Start)
		}
	}

	if tr.End == "" {
		vr.AddError("time ranges end must contain an end")
	} else {
		_, err := time.Parse(format, tr.End)
		if err != nil {
			vr.AddError("end in time range is invalid %q", tr.End)
		}
	}
}

// Limits are used to control acccess for users and importing accounts
// Src is a comma separated list of CIDR specifications
type Limits struct {
	Max     int64       `json:"max,omitempty"`
	Payload int64       `json:"payload,omitempty"`
	Src     string      `json:"src,omitempty"`
	Times   []TimeRange `json:"times,omitempty"`
}

// Validate checks the values in a limit struct
func (l *Limits) Validate(vr *ValidationResults) {
	if l.Max < 0 {
		vr.AddError("limits cannot contain a negative maximum, %d", l.Max)
	}
	if l.Payload < 0 {
		vr.AddError("limits cannot contain a negative payload, %d", l.Payload)
	}

	if l.Src != "" {
		elements := strings.Split(l.Src, ",")

		for _, cidr := range elements {
			cidr = strings.TrimSpace(cidr)
			_, ipNet, err := net.ParseCIDR(cidr)
			if err != nil || ipNet == nil {
				vr.AddError("invalid cidr %q in user src limits", cidr)
			}
		}
	}

	if l.Times != nil && len(l.Times) > 0 {
		for _, t := range l.Times {
			t.Validate(vr)
		}
	}
}

// Permission defines allow/deny subjects
type Permission struct {
	Allow StringList `json:"allow,omitempty"`
	Deny  StringList `json:"deny,omitempty"`
}

// Validate the allow, deny elements of a permission
func (p *Permission) Validate(vr *ValidationResults) {
	for _, subj := range p.Allow {
		Subject(subj).Validate(vr)
	}
	for _, subj := range p.Deny {
		Subject(subj).Validate(vr)
	}
}

// ResponsePermission can be used to allow responses to any reply subject
// that is received on a valid subscription.
type ResponsePermission struct {
	MaxMsgs int           `json:"max"`
	Expires time.Duration `json:"ttl"`
}

// Validate the response permission.
func (p *ResponsePermission) Validate(_ *ValidationResults) {
	// Any values can be valid for now.
}

// Permissions are used to restrict subject access, either on a user or for everyone on a server by default
type Permissions struct {
	Pub  Permission          `json:"pub,omitempty"`
	Sub  Permission          `json:"sub,omitempty"`
	Resp *ResponsePermission `json:"resp,omitempty"`
}

// Validate the pub and sub fields in the permissions list
func (p *Permissions) Validate(vr *ValidationResults) {
	if p.Resp != nil {
		p.Resp.Validate(vr)
	}
}

// StringList is a wrapper for an array of strings
type StringList []string

// Contains returns true if the list contains the string
func (u *StringList) Contains(p string) bool {
	for _, t := range *u {
		if t == p {
			return true
		}
	}
	return false
}

// Add appends 1 or more strings to a list
func (u *StringList) Add(p ...string) {
	for _, v := range p {
		if !u.Contains(v) && v != "" {
			*u = append(*u, v)
		}
	}
}

// Remove removes 1 or more strings from a list
func (u *StringList) Remove(p ...string) {
	for _, v := range p {
		for i, t := range *u {
			if t == v {
				a := *u
				*u = append(a[:i], a[i+1:]...)
				break
			}
		}
	}
}

// TagList is a unique array of lower case strings
// All tag list methods lower case the strings in the arguments
type TagList []string

// Contains returns true if the list contains the tags
func (u *TagList) Contains(p string) bool {
	p = strings.ToLower(p)
	for _, t := range *u {
		if t == p {
			return true
		}
	}
	return false
}

// Add appends 1 or more tags to a list
func (u *TagList) Add(p ...string) {
	for _, v := range p {
		v = strings.ToLower(v)
		if !u.Contains(v) && v != "" {
			*u = append(*u, v)
		}
	}
}

// Remove removes 1 or more tags from a list
func (u *TagList) Remove(p ...string) {
	for _, v := range p {
		v = strings.ToLower(v)
		for i, t := range *u {
			if t == v {
				a := *u
				*u = append(a[:i], a[i+1:]...)
				break
			}
		}
	}
}

// Identity is used to associate an account or operator with a real entity
type Identity struct {
	ID    string `json:"id,omitempty"`
	Proof string `json:"proof,omitempty"`
}

// Validate checks the values in an Identity
func (u *Identity) Validate(_ *ValidationResults) {
	//Fixme identity validation
}
