package database

import (
	"database/sql/driver"
	"encoding/json"
	"fmt"
	"net"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/lib/pq"
	"github.com/sqlc-dev/pqtype"
	"golang.org/x/xerrors"

	"github.com/coder/coder/v2/coderd/rbac"
	"github.com/coder/coder/v2/coderd/rbac/policy"
)

// AuditOAuthConvertState is never stored in the database. It is stored in a cookie
// clientside as a JWT. This type is provided for audit logging purposes.
type AuditOAuthConvertState struct {
	CreatedAt time.Time `db:"created_at" json:"created_at"`
	// The time at which the state string expires, a merge request times out if the user does not perform it quick enough.
	ExpiresAt     time.Time `db:"expires_at" json:"expires_at"`
	FromLoginType LoginType `db:"from_login_type" json:"from_login_type"`
	// The login type the user is converting to. Should be github or oidc.
	ToLoginType LoginType `db:"to_login_type" json:"to_login_type"`
	UserID      uuid.UUID `db:"user_id" json:"user_id"`
}

type HealthSettings struct {
	ID                    uuid.UUID `db:"id" json:"id"`
	DismissedHealthchecks []string  `db:"dismissed_healthchecks" json:"dismissed_healthchecks"`
}

type NotificationsSettings struct {
	ID             uuid.UUID `db:"id" json:"id"`
	NotifierPaused bool      `db:"notifier_paused" json:"notifier_paused"`
}

type PrebuildsSettings struct {
	ID                   uuid.UUID `db:"id" json:"id"`
	ReconciliationPaused bool      `db:"reconciliation_paused" json:"reconciliation_paused"`
}

type Actions []policy.Action

func (a *Actions) Scan(src interface{}) error {
	switch v := src.(type) {
	case string:
		return json.Unmarshal([]byte(v), &a)
	case []byte:
		return json.Unmarshal(v, &a)
	}
	return xerrors.Errorf("unexpected type %T", src)
}

func (a *Actions) Value() (driver.Value, error) {
	return json.Marshal(a)
}

// TemplateACL is a map of ids to permissions.
type TemplateACL map[string][]policy.Action

func (t *TemplateACL) Scan(src interface{}) error {
	switch v := src.(type) {
	case string:
		return json.Unmarshal([]byte(v), &t)
	case []byte:
		return json.Unmarshal(v, &t)
	case json.RawMessage:
		return json.Unmarshal(v, &t)
	}

	return xerrors.Errorf("unexpected type %T", src)
}

func (t TemplateACL) Value() (driver.Value, error) {
	return json.Marshal(t)
}

type ChatACL map[string]ChatACLEntry

func (c *ChatACL) Scan(src interface{}) error {
	switch v := src.(type) {
	case string:
		return json.Unmarshal([]byte(v), &c)
	case []byte:
		return json.Unmarshal(v, &c)
	case json.RawMessage:
		return json.Unmarshal(v, &c)
	}

	return xerrors.Errorf("unexpected type %T", src)
}

//nolint:revive
func (c ChatACL) RBACACL() map[string][]policy.Action {
	rbacACL := make(map[string][]policy.Action, len(c))
	for id, entry := range c {
		rbacACL[id] = entry.Permissions
	}
	return rbacACL
}

func (c ChatACL) Value() (driver.Value, error) {
	if c == nil {
		return json.Marshal(ChatACL{})
	}
	return json.Marshal(c)
}

type ChatACLEntry struct {
	Permissions []policy.Action `json:"permissions"`
}

type WorkspaceACL map[string]WorkspaceACLEntry

func (t *WorkspaceACL) Scan(src interface{}) error {
	switch v := src.(type) {
	case string:
		return json.Unmarshal([]byte(v), &t)
	case []byte:
		return json.Unmarshal(v, &t)
	case json.RawMessage:
		return json.Unmarshal(v, &t)
	}

	return xerrors.Errorf("unexpected type %T", src)
}

//nolint:revive
func (w WorkspaceACL) RBACACL() map[string][]policy.Action {
	// Convert WorkspaceACL to a map of string to []policy.Action.
	// This is used for RBAC checks.
	rbacACL := make(map[string][]policy.Action, len(w))
	for id, entry := range w {
		rbacACL[id] = entry.Permissions
	}
	return rbacACL
}

func (t WorkspaceACL) Value() (driver.Value, error) {
	return json.Marshal(t)
}

type WorkspaceACLEntry struct {
	Permissions []policy.Action `json:"permissions"`
}

// WorkspaceACLDisplayInfo supplements workspace ACLs with the actors'
// display info.  Key is string rather than uuid.UUID as this aligns
// with how RBAC represents actor IDs.
type WorkspaceACLDisplayInfo map[string]struct {
	Name      string `json:"name"`
	AvatarURL string `json:"avatar_url"`
}

// WorkspaceACLDisplayInfo is only used to read from the DB.
func (w *WorkspaceACLDisplayInfo) Scan(src interface{}) error {
	switch v := src.(type) {
	case string:
		return json.Unmarshal([]byte(v), w)
	case []byte:
		return json.Unmarshal(v, w)
	case json.RawMessage:
		return json.Unmarshal(v, w)
	}
	return xerrors.Errorf("unexpected type %T", src)
}

type ExternalAuthProvider struct {
	ID       string `json:"id"`
	Optional bool   `json:"optional,omitempty"`
}

type StringMap map[string]string

func (m *StringMap) Scan(src interface{}) error {
	if src == nil {
		return nil
	}
	switch src := src.(type) {
	case []byte:
		err := json.Unmarshal(src, m)
		if err != nil {
			return err
		}
	default:
		return xerrors.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, m)
	}
	return nil
}

func (m StringMap) Value() (driver.Value, error) {
	return json.Marshal(m)
}

type StringMapOfInt map[string]int64

func (m *StringMapOfInt) Scan(src interface{}) error {
	if src == nil {
		return nil
	}
	switch src := src.(type) {
	case []byte:
		err := json.Unmarshal(src, m)
		if err != nil {
			return err
		}
	default:
		return xerrors.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, m)
	}
	return nil
}

func (m StringMapOfInt) Value() (driver.Value, error) {
	return json.Marshal(m)
}

type CustomRolePermissions []CustomRolePermission

func (s *APIKeyScopes) Scan(src any) error {
	var arr []string
	if err := pq.Array(&arr).Scan(src); err != nil {
		return err
	}
	out := make(APIKeyScopes, len(arr))
	for i, v := range arr {
		out[i] = APIKeyScope(v)
	}
	*s = out
	return nil
}

func (s APIKeyScopes) Value() (driver.Value, error) {
	arr := make([]string, len(s))
	for i, v := range s {
		arr[i] = string(v)
	}
	return pq.Array(arr).Value()
}

func (a *CustomRolePermissions) Scan(src interface{}) error {
	switch v := src.(type) {
	case string:
		return json.Unmarshal([]byte(v), &a)
	case []byte:
		return json.Unmarshal(v, &a)
	}
	return xerrors.Errorf("unexpected type %T", src)
}

func (a CustomRolePermissions) Value() (driver.Value, error) {
	return json.Marshal(a)
}

type CustomRolePermission struct {
	Negate       bool          `json:"negate"`
	ResourceType string        `json:"resource_type"`
	Action       policy.Action `json:"action"`
}

func (a CustomRolePermission) String() string {
	str := a.ResourceType + "." + string(a.Action)
	if a.Negate {
		return "-" + str
	}
	return str
}

// NameOrganizationPair is used as a lookup tuple for custom role rows.
type NameOrganizationPair struct {
	Name string `db:"name" json:"name"`
	// OrganizationID if unset will assume a null column value
	OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"`
}

func (*NameOrganizationPair) Scan(_ interface{}) error {
	return xerrors.Errorf("this should never happen, type 'NameOrganizationPair' should only be used as a parameter")
}

// Value returns the tuple **literal**
// To get the literal value to return, you can use the expression syntax in a psql
// shell.
//
//		SELECT ('customrole'::text,'ece79dac-926e-44ca-9790-2ff7c5eb6e0c'::uuid);
//	To see 'null' option. Using the nil uuid as null to avoid empty string literals for null.
//		SELECT ('customrole',00000000-0000-0000-0000-000000000000);
//
// This value is usually used as an array, NameOrganizationPair[]. You can see
// what that literal is as well, with proper quoting.
//
//	SELECT ARRAY[('customrole'::text,'ece79dac-926e-44ca-9790-2ff7c5eb6e0c'::uuid)];
func (a NameOrganizationPair) Value() (driver.Value, error) {
	return fmt.Sprintf(`(%s,%s)`, a.Name, a.OrganizationID.String()), nil
}

// AgentIDNamePair is used as a result tuple for workspace and agent rows.
type AgentIDNamePair struct {
	ID   uuid.UUID `db:"id" json:"id"`
	Name string    `db:"name" json:"name"`
}

func (p *AgentIDNamePair) Scan(src interface{}) error {
	var v string
	switch a := src.(type) {
	case []byte:
		v = string(a)
	case string:
		v = a
	default:
		return xerrors.Errorf("unexpected type %T", src)
	}
	parts := strings.Split(strings.Trim(v, "()"), ",")
	if len(parts) != 2 {
		return xerrors.New("invalid format for AgentIDNamePair")
	}
	id, err := uuid.Parse(strings.TrimSpace(parts[0]))
	if err != nil {
		return err
	}
	p.ID, p.Name = id, strings.TrimSpace(parts[1])
	return nil
}

func (p AgentIDNamePair) Value() (driver.Value, error) {
	return fmt.Sprintf(`(%s,%s)`, p.ID.String(), p.Name), nil
}

// UserLinkClaims is the returned IDP claims for a given user link.
// These claims are fetched at login time. These are the claims that were
// used for IDP sync.
type UserLinkClaims struct {
	IDTokenClaims  map[string]interface{} `json:"id_token_claims"`
	UserInfoClaims map[string]interface{} `json:"user_info_claims"`
	// MergeClaims are computed in Golang. It is the result of merging
	// the IDTokenClaims and UserInfoClaims. UserInfoClaims take precedence.
	MergedClaims map[string]interface{} `json:"merged_claims"`
}

func (a *UserLinkClaims) Scan(src interface{}) error {
	switch v := src.(type) {
	case string:
		return json.Unmarshal([]byte(v), &a)
	case []byte:
		return json.Unmarshal(v, &a)
	}
	return xerrors.Errorf("unexpected type %T", src)
}

func (a UserLinkClaims) Value() (driver.Value, error) {
	return json.Marshal(a)
}

func ParseIP(ipStr string) pqtype.Inet {
	ip := net.ParseIP(ipStr)
	ipNet := net.IPNet{}
	if ip != nil {
		ipNet = net.IPNet{
			IP:   ip,
			Mask: net.CIDRMask(len(ip)*8, len(ip)*8),
		}
	}

	return pqtype.Inet{
		IPNet: ipNet,
		Valid: ip != nil,
	}
}

// AllowList is a typed wrapper around a list of AllowListTarget entries.
// It implements sql.Scanner and driver.Valuer so it can be stored in and
// loaded from a Postgres text[] column that stores each entry in the
// canonical form "type:id".
type AllowList []rbac.AllowListElement

// Scan implements sql.Scanner. It supports inputs that pq.Array can decode
// into []string, and then converts each element to an AllowListTarget.
func (a *AllowList) Scan(src any) error {
	var raw []string
	if err := pq.Array(&raw).Scan(src); err != nil {
		return err
	}
	out := make([]rbac.AllowListElement, len(raw))
	for i, s := range raw {
		e, err := rbac.ParseAllowListEntry(s)
		if err != nil {
			return err
		}
		out[i] = e
	}
	*a = out
	return nil
}

// Value implements driver.Valuer by converting the list to []string using the
// canonical "type:id" form and delegating to pq.Array for encoding.
func (a AllowList) Value() (driver.Value, error) {
	raw := make([]string, len(a))
	for i, t := range a {
		raw[i] = t.String()
	}
	return pq.Array(raw).Value()
}
