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

package postgres_test

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/0x6flab/namegenerator"
	"github.com/absmach/magistrala/pkg/errors"
	repoerr "github.com/absmach/magistrala/pkg/errors/repository"
	"github.com/absmach/magistrala/pkg/schedule"
	"github.com/absmach/magistrala/pkg/uuid"
	"github.com/absmach/magistrala/reports"
	"github.com/absmach/magistrala/reports/postgres"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var (
	namegen    = namegenerator.NewGenerator()
	idProvider = uuid.New()
)

func generateUUID(t *testing.T) string {
	id, err := idProvider.ID()
	require.Nil(t, err, fmt.Sprintf("generate uuid unexpected error: %s", err))
	return id
}

func TestAddReportConfig(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	reportConfig := reports.ReportConfig{
		ID:          generateUUID(t),
		Name:        namegen.Generate(),
		Description: namegen.Generate(),
		DomainID:    generateUUID(t),
		Config: &reports.MetricConfig{
			From:  "now-1h",
			To:    "now",
			Title: "Test Report",
		},
		Metrics: []reports.ReqMetric{
			{
				ChannelID: generateUUID(t),
				Name:      "temperature",
			},
		},
		Email: &reports.EmailSetting{
			To:      []string{"test@example.com"},
			Subject: "Test Report",
			Content: "Report content",
		},
		Schedule: schedule.Schedule{
			StartDateTime:   time.Now().UTC(),
			Time:            time.Now().UTC().Add(time.Hour),
			Recurring:       schedule.Daily,
			RecurringPeriod: 1,
		},
		Status:    reports.EnabledStatus,
		CreatedAt: time.Now().UTC(),
		CreatedBy: generateUUID(t),
		UpdatedAt: time.Now().UTC(),
		UpdatedBy: generateUUID(t),
	}

	cases := []struct {
		desc   string
		report reports.ReportConfig
		err    error
	}{
		{
			desc:   "add valid report config",
			report: reportConfig,
			err:    nil,
		},
		{
			desc:   "add duplicate report config",
			report: reportConfig,
			err:    repoerr.ErrConflict,
		},
		{
			desc: "add report config with empty ID",
			report: reports.ReportConfig{
				Name:      namegen.Generate(),
				DomainID:  generateUUID(t),
				CreatedAt: time.Now().UTC(),
				UpdatedAt: time.Now().UTC(),
			},
			err: repoerr.ErrCreateEntity,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			rpt, err := repo.AddReportConfig(context.Background(), tc.report)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
			require.NotEmpty(t, rpt.ID)
			require.Equal(t, tc.report.Name, rpt.Name)
			require.Equal(t, tc.report.DomainID, rpt.DomainID)
			require.Equal(t, tc.report.Status, rpt.Status)
		})
	}
}

func TestViewReportConfig(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	reportConfig := reports.ReportConfig{
		ID:          generateUUID(t),
		Name:        namegen.Generate(),
		Description: namegen.Generate(),
		DomainID:    generateUUID(t),
		Config: &reports.MetricConfig{
			From:  "now-1h",
			To:    "now",
			Title: "Test Report",
		},
		Metrics: []reports.ReqMetric{
			{
				ChannelID: generateUUID(t),
				Name:      "temperature",
			},
		},
		Email: &reports.EmailSetting{
			To:      []string{"test@example.com"},
			Subject: "Test Report",
		},
		Status:    reports.EnabledStatus,
		CreatedAt: time.Now().UTC(),
		CreatedBy: generateUUID(t),
		UpdatedAt: time.Now().UTC(),
		UpdatedBy: generateUUID(t),
	}

	saved, err := repo.AddReportConfig(context.Background(), reportConfig)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc string
		id   string
		err  error
	}{
		{
			desc: "view existing report config",
			id:   saved.ID,
			err:  nil,
		},
		{
			desc: "view non-existing report config",
			id:   generateUUID(t),
			err:  repoerr.ErrNotFound,
		},
		{
			desc: "view with empty id",
			id:   "",
			err:  repoerr.ErrNotFound,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			rpt, err := repo.ViewReportConfig(context.Background(), tc.id)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
			require.Equal(t, saved.ID, rpt.ID)
			require.Equal(t, saved.Name, rpt.Name)
			require.Equal(t, saved.DomainID, rpt.DomainID)
		})
	}
}

func TestUpdateReportConfig(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	reportConfig := reports.ReportConfig{
		ID:          generateUUID(t),
		Name:        namegen.Generate(),
		Description: namegen.Generate(),
		DomainID:    generateUUID(t),
		Status:      reports.EnabledStatus,
		CreatedAt:   time.Now().UTC(),
		CreatedBy:   generateUUID(t),
		UpdatedAt:   time.Now().UTC(),
		UpdatedBy:   generateUUID(t),
		Metrics: []reports.ReqMetric{
			{
				ChannelID: generateUUID(t),
				Name:      "temperature",
			},
		},
	}

	saved, err := repo.AddReportConfig(context.Background(), reportConfig)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc   string
		report reports.ReportConfig
		err    error
	}{
		{
			desc: "update report name",
			report: reports.ReportConfig{
				ID:        saved.ID,
				Name:      "Updated Name",
				UpdatedAt: time.Now().UTC(),
				UpdatedBy: generateUUID(t),
			},
			err: nil,
		},
		{
			desc: "update report description",
			report: reports.ReportConfig{
				ID:          saved.ID,
				Description: "Updated Description",
				UpdatedAt:   time.Now().UTC(),
				UpdatedBy:   generateUUID(t),
			},
			err: nil,
		},
		{
			desc: "update non-existing report",
			report: reports.ReportConfig{
				ID:        generateUUID(t),
				Name:      "New Name",
				UpdatedAt: time.Now().UTC(),
			},
			err: repoerr.ErrNotFound,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			rpt, err := repo.UpdateReportConfig(context.Background(), tc.report)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
			require.Equal(t, tc.report.ID, rpt.ID)
			if tc.report.Name != "" {
				require.Equal(t, tc.report.Name, rpt.Name)
			}
			if tc.report.Description != "" {
				require.Equal(t, tc.report.Description, rpt.Description)
			}
		})
	}
}

func TestUpdateReportConfigStatus(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	reportConfig := reports.ReportConfig{
		ID:        generateUUID(t),
		Name:      namegen.Generate(),
		DomainID:  generateUUID(t),
		Status:    reports.EnabledStatus,
		CreatedAt: time.Now().UTC(),
		CreatedBy: generateUUID(t),
		UpdatedAt: time.Now().UTC(),
		UpdatedBy: generateUUID(t),
		Metrics:   []reports.ReqMetric{},
	}

	saved, err := repo.AddReportConfig(context.Background(), reportConfig)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc   string
		report reports.ReportConfig
		err    error
	}{
		{
			desc: "disable report",
			report: reports.ReportConfig{
				ID:        saved.ID,
				Status:    reports.DisabledStatus,
				UpdatedAt: time.Now().UTC(),
				UpdatedBy: generateUUID(t),
			},
			err: nil,
		},
		{
			desc: "enable report",
			report: reports.ReportConfig{
				ID:        saved.ID,
				Status:    reports.EnabledStatus,
				UpdatedAt: time.Now().UTC(),
				UpdatedBy: generateUUID(t),
			},
			err: nil,
		},
		{
			desc: "update status of non-existing report",
			report: reports.ReportConfig{
				ID:        generateUUID(t),
				Status:    reports.DisabledStatus,
				UpdatedAt: time.Now().UTC(),
			},
			err: repoerr.ErrNotFound,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			rpt, err := repo.UpdateReportConfigStatus(context.Background(), tc.report)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
			require.Equal(t, tc.report.Status, rpt.Status)
		})
	}
}

func TestRemoveReportConfig(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	reportConfig := reports.ReportConfig{
		ID:        generateUUID(t),
		Name:      namegen.Generate(),
		DomainID:  generateUUID(t),
		Status:    reports.EnabledStatus,
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
		Metrics:   []reports.ReqMetric{},
	}

	saved, err := repo.AddReportConfig(context.Background(), reportConfig)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc string
		id   string
		err  error
	}{
		{
			desc: "remove existing report",
			id:   saved.ID,
			err:  nil,
		},
		{
			desc: "remove non-existing report",
			id:   generateUUID(t),
			err:  nil,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			err := repo.RemoveReportConfig(context.Background(), tc.id)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
		})
	}
}

func TestListReportsConfig(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	domainID := generateUUID(t)

	num := uint64(10)
	for i := uint64(0); i < num; i++ {
		reportConfig := reports.ReportConfig{
			ID:        generateUUID(t),
			Name:      fmt.Sprintf("Report-%d", i),
			DomainID:  domainID,
			Status:    reports.EnabledStatus,
			CreatedAt: time.Now().UTC(),
			UpdatedAt: time.Now().UTC(),
			Metrics:   []reports.ReqMetric{},
		}
		_, err := repo.AddReportConfig(context.Background(), reportConfig)
		require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
	}

	cases := []struct {
		desc     string
		pageMeta reports.PageMeta
		size     uint64
		err      error
	}{
		{
			desc: "list all reports",
			pageMeta: reports.PageMeta{
				Domain: domainID,
				Limit:  num,
				Offset: 0,
			},
			size: num,
			err:  nil,
		},
		{
			desc: "list with limit",
			pageMeta: reports.PageMeta{
				Domain: domainID,
				Limit:  5,
				Offset: 0,
			},
			size: 5,
			err:  nil,
		},
		{
			desc: "list with offset",
			pageMeta: reports.PageMeta{
				Domain: domainID,
				Limit:  num,
				Offset: 5,
			},
			size: 5,
			err:  nil,
		},
		{
			desc: "list enabled reports",
			pageMeta: reports.PageMeta{
				Domain: domainID,
				Limit:  num,
				Status: reports.EnabledStatus,
			},
			size: num,
			err:  nil,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			page, err := repo.ListAllReportsConfig(context.Background(), tc.pageMeta)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
			require.Equal(t, tc.size, uint64(len(page.ReportConfigs)))
		})
	}
}

func TestListUserReportsConfig(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM domains_role_actions")
		require.Nil(t, err, fmt.Sprintf("clean domains_role_actions unexpected error: %s", err))
		_, err = db.Exec("DELETE FROM domains_role_members")
		require.Nil(t, err, fmt.Sprintf("clean domains_role_members unexpected error: %s", err))
		_, err = db.Exec("DELETE FROM domains_roles")
		require.Nil(t, err, fmt.Sprintf("clean domains_roles unexpected error: %s", err))
		_, err = db.Exec("DELETE FROM domains")
		require.Nil(t, err, fmt.Sprintf("clean domains unexpected error: %s", err))
		_, err = db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	domainID := generateUUID(t)
	domainRoute := generateUUID(t)
	userID := generateUUID(t)
	domainUserID := generateUUID(t)
	otherUserID := generateUUID(t)

	_, err := db.Exec(`INSERT INTO domains (id, name, route, status) VALUES ($1, $2, $3, $4)`, domainID, namegen.Generate(), domainRoute, 0)
	require.Nil(t, err, fmt.Sprintf("insert domains unexpected error: %s", err))

	num := 10
	var allCfgs []reports.ReportConfig
	for i := range num {
		cfg := reports.ReportConfig{
			ID:        generateUUID(t),
			Name:      fmt.Sprintf("Report-%d", i),
			DomainID:  domainID,
			Status:    reports.EnabledStatus,
			CreatedAt: time.Now().UTC().Add(time.Duration(i) * time.Minute),
			UpdatedAt: time.Now().UTC().Add(time.Duration(i) * time.Minute),
			Metrics:   []reports.ReqMetric{},
		}
		cfg, err := repo.AddReportConfig(context.Background(), cfg)
		require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
		allCfgs = append(allCfgs, cfg)
	}

	// Assign userID to the first 5 report configs via direct role INSERT.
	for i := range 5 {
		roleID := generateUUID(t)
		_, err := db.Exec(`INSERT INTO reports_roles (id, name, entity_id) VALUES ($1, $2, $3)`, roleID, "admin", allCfgs[i].ID)
		require.Nil(t, err, fmt.Sprintf("insert reports_roles unexpected error: %s", err))
		_, err = db.Exec(`INSERT INTO reports_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, roleID, userID, allCfgs[i].ID)
		require.Nil(t, err, fmt.Sprintf("insert reports_role_members unexpected error: %s", err))
	}

	domainRoleID := generateUUID(t)
	_, err = db.Exec(`INSERT INTO domains_roles (id, name, entity_id) VALUES ($1, $2, $3)`, domainRoleID, "admin", domainID)
	require.Nil(t, err, fmt.Sprintf("insert domains_roles unexpected error: %s", err))
	_, err = db.Exec(`INSERT INTO domains_role_members (role_id, member_id, entity_id) VALUES ($1, $2, $3)`, domainRoleID, domainUserID, domainID)
	require.Nil(t, err, fmt.Sprintf("insert domains_role_members unexpected error: %s", err))
	_, err = db.Exec(`INSERT INTO domains_role_actions (role_id, action) VALUES ($1, $2)`, domainRoleID, "report_read")
	require.Nil(t, err, fmt.Sprintf("insert domains_role_actions unexpected error: %s", err))

	cases := []struct {
		desc     string
		userID   string
		pageMeta reports.PageMeta
		size     int
		err      error
	}{
		{
			desc:   "list user reports returns only accessible reports",
			userID: userID,
			pageMeta: reports.PageMeta{
				Domain: domainID,
				Limit:  100,
				Offset: 0,
			},
			size: 5,
			err:  nil,
		},
		{
			desc:   "list user reports with limit",
			userID: userID,
			pageMeta: reports.PageMeta{
				Domain: domainID,
				Limit:  3,
				Offset: 0,
			},
			size: 3,
			err:  nil,
		},
		{
			desc:   "list user reports with offset",
			userID: userID,
			pageMeta: reports.PageMeta{
				Domain: domainID,
				Limit:  100,
				Offset: 3,
			},
			size: 2,
			err:  nil,
		},
		{
			desc:   "list user reports with enabled status filter",
			userID: userID,
			pageMeta: reports.PageMeta{
				Domain: domainID,
				Limit:  100,
				Status: reports.EnabledStatus,
			},
			size: 5,
			err:  nil,
		},
		{
			desc:   "list reports for user with no role assignments returns 0",
			userID: otherUserID,
			pageMeta: reports.PageMeta{
				Domain: domainID,
				Limit:  100,
				Offset: 0,
			},
			size: 0,
			err:  nil,
		},
		{
			desc:   "list user reports via domain role returns all domain reports",
			userID: domainUserID,
			pageMeta: reports.PageMeta{
				Domain: domainID,
				Limit:  100,
				Offset: 0,
			},
			size: 10,
			err:  nil,
		},
		{
			desc:   "list user reports with non-existing domain returns 0",
			userID: userID,
			pageMeta: reports.PageMeta{
				Domain: generateUUID(t),
				Limit:  100,
				Offset: 0,
			},
			size: 0,
			err:  nil,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			page, err := repo.ListUserReportsConfig(context.Background(), tc.userID, tc.pageMeta)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
			require.Equal(t, tc.size, len(page.ReportConfigs), fmt.Sprintf("%s: expected %d reports, got %d", tc.desc, tc.size, len(page.ReportConfigs)))
		})
	}
}

func TestUpdateReportSchedule(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	reportConfig := reports.ReportConfig{
		ID:        generateUUID(t),
		Name:      namegen.Generate(),
		DomainID:  generateUUID(t),
		Status:    reports.EnabledStatus,
		CreatedAt: time.Now().UTC(),
		CreatedBy: generateUUID(t),
		UpdatedAt: time.Now().UTC(),
		UpdatedBy: generateUUID(t),
		Metrics: []reports.ReqMetric{
			{
				ChannelID: generateUUID(t),
				Name:      "temperature",
			},
		},
		Schedule: schedule.Schedule{
			StartDateTime:   time.Now().UTC(),
			Time:            time.Now().UTC().Add(time.Hour),
			Recurring:       schedule.Daily,
			RecurringPeriod: 1,
		},
	}

	saved, err := repo.AddReportConfig(context.Background(), reportConfig)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	newSchedule := schedule.Schedule{
		StartDateTime:   time.Now().UTC().Add(24 * time.Hour),
		Time:            time.Now().UTC().Add(25 * time.Hour),
		Recurring:       schedule.Weekly,
		RecurringPeriod: 2,
	}

	cases := []struct {
		desc     string
		report   reports.ReportConfig
		expected schedule.Schedule
		err      error
	}{
		{
			desc: "update schedule",
			report: reports.ReportConfig{
				ID:        saved.ID,
				Schedule:  newSchedule,
				UpdatedAt: time.Now().UTC(),
				UpdatedBy: generateUUID(t),
			},
			expected: newSchedule,
			err:      nil,
		},
		{
			desc: "update schedule of non-existing report",
			report: reports.ReportConfig{
				ID:        generateUUID(t),
				Schedule:  newSchedule,
				UpdatedAt: time.Now().UTC(),
			},
			err: repoerr.ErrNotFound,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			rpt, err := repo.UpdateReportSchedule(context.Background(), tc.report)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
			require.Equal(t, tc.expected.Recurring, rpt.Schedule.Recurring)
			require.Equal(t, tc.expected.RecurringPeriod, rpt.Schedule.RecurringPeriod)
		})
	}
}

func TestUpdateReportDue(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	reportConfig := reports.ReportConfig{
		ID:        generateUUID(t),
		Name:      namegen.Generate(),
		DomainID:  generateUUID(t),
		Status:    reports.EnabledStatus,
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
		Metrics: []reports.ReqMetric{
			{
				ChannelID: generateUUID(t),
				Name:      "temperature",
			},
		},
	}

	saved, err := repo.AddReportConfig(context.Background(), reportConfig)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	newDue := time.Now().UTC().Add(24 * time.Hour).Truncate(time.Microsecond)

	cases := []struct {
		desc string
		id   string
		due  time.Time
		err  error
	}{
		{
			desc: "update due time",
			id:   saved.ID,
			due:  newDue,
			err:  nil,
		},
		{
			desc: "update due time of non-existing report",
			id:   generateUUID(t),
			due:  newDue,
			err:  repoerr.ErrNotFound,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			rpt, err := repo.UpdateReportDue(context.Background(), tc.id, tc.due)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
			require.True(t, tc.due.Equal(rpt.Schedule.Time))
		})
	}
}

func TestUpdateReportTemplate(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	domainID := generateUUID(t)
	reportConfig := reports.ReportConfig{
		ID:        generateUUID(t),
		Name:      namegen.Generate(),
		DomainID:  domainID,
		Status:    reports.EnabledStatus,
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
		Metrics: []reports.ReqMetric{
			{
				ChannelID: generateUUID(t),
				Name:      "temperature",
			},
		},
	}

	saved, err := repo.AddReportConfig(context.Background(), reportConfig)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	template := reports.ReportTemplate("<html><body>Test Template</body></html>")

	cases := []struct {
		desc     string
		domainID string
		reportID string
		template reports.ReportTemplate
		err      error
	}{
		{
			desc:     "update template",
			domainID: domainID,
			reportID: saved.ID,
			template: template,
			err:      nil,
		},
		{
			desc:     "update template for non-existing report",
			domainID: domainID,
			reportID: generateUUID(t),
			template: template,
			err:      nil,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			err := repo.UpdateReportTemplate(context.Background(), tc.domainID, tc.reportID, tc.template)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
		})
	}
}

func TestViewReportTemplate(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	domainID := generateUUID(t)
	template := reports.ReportTemplate("<html><body>Test Template</body></html>")

	reportConfig := reports.ReportConfig{
		ID:             generateUUID(t),
		Name:           namegen.Generate(),
		DomainID:       domainID,
		Status:         reports.EnabledStatus,
		CreatedAt:      time.Now().UTC(),
		UpdatedAt:      time.Now().UTC(),
		ReportTemplate: template,
		Metrics: []reports.ReqMetric{
			{
				ChannelID: generateUUID(t),
				Name:      "temperature",
			},
		},
	}

	saved, err := repo.AddReportConfig(context.Background(), reportConfig)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc     string
		domainID string
		reportID string
		expected reports.ReportTemplate
		err      error
	}{
		{
			desc:     "view existing template",
			domainID: domainID,
			reportID: saved.ID,
			expected: template,
			err:      nil,
		},
		{
			desc:     "view template for non-existing report",
			domainID: domainID,
			reportID: generateUUID(t),
			err:      repoerr.ErrNotFound,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			tmpl, err := repo.ViewReportTemplate(context.Background(), tc.domainID, tc.reportID)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))
			require.Equal(t, tc.expected, tmpl)
		})
	}
}

func TestDeleteReportTemplate(t *testing.T) {
	t.Cleanup(func() {
		_, err := db.Exec("DELETE FROM report_config")
		require.Nil(t, err, fmt.Sprintf("clean report_config unexpected error: %s", err))
	})

	repo := postgres.NewRepository(database)

	domainID := generateUUID(t)
	template := reports.ReportTemplate("<html><body>Test Template</body></html>")

	reportConfig := reports.ReportConfig{
		ID:             generateUUID(t),
		Name:           namegen.Generate(),
		DomainID:       domainID,
		Status:         reports.EnabledStatus,
		CreatedAt:      time.Now().UTC(),
		UpdatedAt:      time.Now().UTC(),
		ReportTemplate: template,
		Metrics: []reports.ReqMetric{
			{
				ChannelID: generateUUID(t),
				Name:      "temperature",
			},
		},
	}

	saved, err := repo.AddReportConfig(context.Background(), reportConfig)
	require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

	cases := []struct {
		desc     string
		domainID string
		reportID string
		err      error
	}{
		{
			desc:     "delete existing template",
			domainID: domainID,
			reportID: saved.ID,
			err:      nil,
		},
		{
			desc:     "delete template for non-existing report",
			domainID: domainID,
			reportID: generateUUID(t),
			err:      nil,
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			err := repo.DeleteReportTemplate(context.Background(), tc.domainID, tc.reportID)
			if tc.err != nil {
				assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
				return
			}
			require.Nil(t, err, fmt.Sprintf("unexpected error: %s", err))

			if tc.reportID == saved.ID {
				tmpl, err := repo.ViewReportTemplate(context.Background(), tc.domainID, tc.reportID)
				require.Nil(t, err)
				require.Empty(t, tmpl)
			}
		})
	}
}
