// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0

package postgres

import (
	"database/sql"
	"encoding/json"
	"time"

	"github.com/absmach/magistrala/pkg/errors"
	"github.com/absmach/magistrala/pkg/roles"
	"github.com/absmach/magistrala/pkg/schedule"
	"github.com/absmach/magistrala/re"
	"github.com/jackc/pgtype"
	"github.com/lib/pq"
)

// dbRule represents the database structure for a Rule.
type dbRule struct {
	ID                        string             `db:"id"`
	Name                      string             `db:"name"`
	DomainID                  string             `db:"domain_id"`
	Tags                      pgtype.TextArray   `db:"tags,omitempty"`
	Metadata                  []byte             `db:"metadata,omitempty"`
	InputChannel              string             `db:"input_channel"`
	InputTopic                sql.NullString     `db:"input_topic"`
	LogicType                 re.ScriptType      `db:"logic_type"`
	LogicValue                string             `db:"logic_value"`
	Outputs                   []byte             `db:"outputs"`
	StartDateTime             sql.NullTime       `db:"start_datetime"`
	Time                      sql.NullTime       `db:"time"`
	Recurring                 schedule.Recurring `db:"recurring"`
	RecurringPeriod           uint               `db:"recurring_period"`
	Status                    re.Status          `db:"status"`
	CreatedAt                 time.Time          `db:"created_at"`
	CreatedBy                 string             `db:"created_by"`
	UpdatedAt                 time.Time          `db:"updated_at"`
	UpdatedBy                 string             `db:"updated_by"`
	MemberID                  string             `db:"member_id,omitempty"`
	RoleID                    string             `db:"role_id,omitempty"`
	RoleName                  string             `db:"role_name,omitempty"`
	Actions                   pq.StringArray     `db:"actions,omitempty"`
	AccessType                string             `db:"access_type,omitempty"`
	AccessProviderId          string             `db:"access_provider_id,omitempty"`
	AccessProviderRoleId      string             `db:"access_provider_role_id,omitempty"`
	AccessProviderRoleName    string             `db:"access_provider_role_name,omitempty"`
	AccessProviderRoleActions pq.StringArray     `db:"access_provider_role_actions,omitempty"`
	Roles                     json.RawMessage    `db:"roles,omitempty"`
}

func ruleToDb(r re.Rule) (dbRule, error) {
	metadata := []byte("{}")
	if len(r.Metadata) > 0 {
		b, err := json.Marshal(r.Metadata)
		if err != nil {
			return dbRule{}, errors.Wrap(errors.ErrMalformedEntity, err)
		}
		metadata = b
	}

	start := sql.NullTime{Time: r.Schedule.StartDateTime}
	if !r.Schedule.StartDateTime.IsZero() {
		start.Valid = true
	}
	t := sql.NullTime{Time: r.Schedule.Time}
	if !r.Schedule.Time.IsZero() {
		t.Valid = true
	}
	var tags pgtype.TextArray
	if err := tags.Set(r.Tags); err != nil {
		return dbRule{}, err
	}

	outputs, err := json.Marshal(r.Outputs)
	if err != nil {
		return dbRule{}, errors.Wrap(errors.ErrMalformedEntity, err)
	}

	return dbRule{
		ID:              r.ID,
		Name:            r.Name,
		DomainID:        r.DomainID,
		Tags:            tags,
		Metadata:        metadata,
		InputChannel:    r.InputChannel,
		InputTopic:      toNullString(r.InputTopic),
		LogicType:       r.Logic.Type,
		LogicValue:      r.Logic.Value,
		Outputs:         outputs,
		StartDateTime:   start,
		Time:            t,
		Recurring:       r.Schedule.Recurring,
		RecurringPeriod: r.Schedule.RecurringPeriod,
		Status:          r.Status,
		CreatedAt:       r.CreatedAt,
		CreatedBy:       r.CreatedBy,
		UpdatedAt:       r.UpdatedAt,
		UpdatedBy:       r.UpdatedBy,
	}, nil
}

func dbToRule(dto dbRule) (re.Rule, error) {
	var metadata re.Metadata
	if dto.Metadata != nil {
		if err := json.Unmarshal(dto.Metadata, &metadata); err != nil {
			return re.Rule{}, errors.Wrap(errors.ErrMalformedEntity, err)
		}
	}

	var tags []string
	for _, e := range dto.Tags.Elements {
		tags = append(tags, e.String)
	}

	var outputs re.Outputs
	if dto.Outputs != nil {
		if err := json.Unmarshal(dto.Outputs, &outputs); err != nil {
			return re.Rule{}, errors.Wrap(errors.ErrMalformedEntity, err)
		}
	}

	var roles []roles.MemberRoleActions
	if dto.Roles != nil {
		if err := json.Unmarshal(dto.Roles, &roles); err != nil {
			return re.Rule{}, errors.Wrap(errors.ErrMalformedEntity, err)
		}
	}

	return re.Rule{
		ID:           dto.ID,
		Name:         dto.Name,
		DomainID:     dto.DomainID,
		Tags:         tags,
		Metadata:     metadata,
		InputChannel: dto.InputChannel,
		InputTopic:   fromNullString(dto.InputTopic),
		Logic: re.Script{
			Type:  dto.LogicType,
			Value: dto.LogicValue,
		},
		Outputs: outputs,
		Schedule: schedule.Schedule{
			StartDateTime:   dto.StartDateTime.Time,
			Time:            dto.Time.Time,
			Recurring:       dto.Recurring,
			RecurringPeriod: dto.RecurringPeriod,
		},
		Status:                    dto.Status,
		CreatedAt:                 dto.CreatedAt,
		CreatedBy:                 dto.CreatedBy,
		UpdatedAt:                 dto.UpdatedAt,
		UpdatedBy:                 dto.UpdatedBy,
		RoleID:                    dto.RoleID,
		RoleName:                  dto.RoleName,
		Actions:                   []string(dto.Actions),
		AccessType:                dto.AccessType,
		AccessProviderId:          dto.AccessProviderId,
		AccessProviderRoleId:      dto.AccessProviderRoleId,
		AccessProviderRoleName:    dto.AccessProviderRoleName,
		AccessProviderRoleActions: []string(dto.AccessProviderRoleActions),
		Roles:                     roles,
	}, nil
}

func toNullString(value string) sql.NullString {
	if value == "" {
		return sql.NullString{Valid: false}
	}
	return sql.NullString{String: value, Valid: true}
}

func fromNullString(nullString sql.NullString) string {
	if !nullString.Valid {
		return ""
	}
	return nullString.String
}
