package tickets

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

	"github.com/TaruLab/keluargamart-backend/internal/auth"
)

type mockMailer struct {
	sent []sentMail
}

type sentMail struct {
	to      string
	subject string
	body    string
}

func (m *mockMailer) Send(_ context.Context, to, subject, body string) error {
	m.sent = append(m.sent, sentMail{to: to, subject: subject, body: body})
	return nil
}

type mockRepo struct {
	tickets  map[int64]*Ticket
	comments map[int64][]TicketComment
	seq      int64
}

func newMockRepo() *mockRepo {
	return &mockRepo{
		tickets:  make(map[int64]*Ticket),
		comments: make(map[int64][]TicketComment),
	}
}

func (m *mockRepo) CreateTicket(ctx context.Context, t *Ticket) (*Ticket, error) {
	m.seq++
	t.ID = m.seq
	t.TicketNumber = fmt.Sprintf("TKT-20261102-%04d", m.seq)
	t.CreatedAt = time.Now()
	t.UpdatedAt = time.Now()
	m.tickets[t.ID] = t
	return t, nil
}

func (m *mockRepo) GetTicketByID(ctx context.Context, id int64) (*Ticket, error) {
	t, ok := m.tickets[id]
	if !ok {
		return nil, ErrTicketNotFound
	}
	return t, nil
}

func (m *mockRepo) ListTickets(ctx context.Context, filter ListTicketsFilter) ([]Ticket, int64, error) {
	var list []Ticket
	for _, t := range m.tickets {
		list = append(list, *t)
	}
	return list, int64(len(list)), nil
}

func (m *mockRepo) UpdateTicket(ctx context.Context, id int64, req UpdateTicketRequest) (*Ticket, error) {
	t, ok := m.tickets[id]
	if !ok {
		return nil, ErrTicketNotFound
	}
	if req.Status != nil {
		t.Status = *req.Status
	}
	if req.Priority != nil {
		t.Priority = *req.Priority
	}
	t.UpdatedAt = time.Now()
	return t, nil
}

func (m *mockRepo) CreateComment(ctx context.Context, c *TicketComment) (*TicketComment, error) {
	c.ID = int64(len(m.comments[c.TicketID]) + 1)
	c.CreatedAt = time.Now()
	m.comments[c.TicketID] = append(m.comments[c.TicketID], *c)
	return c, nil
}

func (m *mockRepo) ListCommentsByTicketID(ctx context.Context, ticketID int64, includeInternal bool) ([]TicketComment, error) {
	all := m.comments[ticketID]
	var res []TicketComment
	for _, c := range all {
		if !includeInternal && c.IsInternal {
			continue
		}
		res = append(res, c)
	}
	return res, nil
}

func TestService_Validations(t *testing.T) {
	svc := NewService(nil)
	ctx := context.Background()
	user := &auth.User{ID: 1, Name: "Tester", Email: "tester@keluargamart.id", Role: auth.RoleEmployee}

	// 1. Title required
	_, err := svc.Create(ctx, user, CreateTicketRequest{Title: "", Description: "Test Desc"})
	if err != ErrTitleRequired {
		t.Fatalf("expected ErrTitleRequired, got %v", err)
	}

	// 2. Description required
	_, err = svc.Create(ctx, user, CreateTicketRequest{Title: "Test Title", Description: ""})
	if err != ErrDescriptionRequired {
		t.Fatalf("expected ErrDescriptionRequired, got %v", err)
	}

	// 3. Invalid category
	_, err = svc.Create(ctx, user, CreateTicketRequest{Title: "Test", Description: "Test", Category: "invalid_cat"})
	if err != ErrInvalidCategory {
		t.Fatalf("expected ErrInvalidCategory, got %v", err)
	}

	// 4. Invalid priority
	_, err = svc.Create(ctx, user, CreateTicketRequest{Title: "Test", Description: "Test", Priority: "ultra_high"})
	if err != ErrInvalidPriority {
		t.Fatalf("expected ErrInvalidPriority, got %v", err)
	}

	// 5. Update without manage permission
	_, err = svc.Update(ctx, user, 10, UpdateTicketRequest{})
	if err != ErrUnauthorized {
		t.Fatalf("expected ErrUnauthorized, got %v", err)
	}

	// 6. Comment required
	_, err = svc.AddComment(ctx, user, 10, CreateCommentRequest{Comment: "   "})
	if err != ErrCommentRequired {
		t.Fatalf("expected ErrCommentRequired, got %v", err)
	}
}

func TestService_CreateTicket_EmailDispatch(t *testing.T) {
	repo := newMockRepo()
	mailer := &mockMailer{}
	svc := NewService(repo).WithMailer(mailer, "support@keluargamart.id").WithSyncEmail()

	ctx := context.Background()
	user := &auth.User{ID: 42, Name: "Budi Santoso", Email: "budi@keluargamart.id", Role: auth.RoleCashier}

	created, err := svc.Create(ctx, user, CreateTicketRequest{
		Title:       "Mesin Barcode Kasir 1 Error",
		Description: "Scanner barcode tidak bisa membaca kode produk sejak pagi.",
		Category:    "hardware",
		Priority:    "high",
		SourceApp:   "pos",
		SourcePath:  "/pos/register",
	})
	if err != nil {
		t.Fatalf("unexpected error creating ticket: %v", err)
	}

	if created.TicketNumber != "TKT-20261102-0001" {
		t.Fatalf("expected ticket number TKT-20261102-0001, got %s", created.TicketNumber)
	}

	// Should dispatch 2 emails: 1 to reporter (budi@keluargamart.id), 1 to support@keluargamart.id
	if len(mailer.sent) != 2 {
		t.Fatalf("expected 2 emails sent, got %d", len(mailer.sent))
	}

	// Email to reporter
	userMail := mailer.sent[0]
	if userMail.to != "budi@keluargamart.id" {
		t.Fatalf("expected first recipient budi@keluargamart.id, got %s", userMail.to)
	}
	if !strings.Contains(userMail.subject, "TKT-20261102-0001") {
		t.Fatalf("expected subject to contain ticket number, got: %s", userMail.subject)
	}
	if !strings.Contains(userMail.body, "Mesin Barcode Kasir 1 Error") || !strings.Contains(userMail.body, "hardware") {
		t.Fatalf("expected body to contain title and category, got: %s", userMail.body)
	}

	// Email to support
	supportMail := mailer.sent[1]
	if supportMail.to != "support@keluargamart.id" {
		t.Fatalf("expected second recipient support@keluargamart.id, got %s", supportMail.to)
	}
	if !strings.Contains(supportMail.subject, "TKT-20261102-0001") {
		t.Fatalf("expected subject to contain ticket number, got: %s", supportMail.subject)
	}
	if !strings.Contains(supportMail.body, "Budi Santoso (budi@keluargamart.id)") {
		t.Fatalf("expected body to contain reporter details, got: %s", supportMail.body)
	}
}

func TestService_AddComment_EmailDispatch(t *testing.T) {
	repo := newMockRepo()
	mailer := &mockMailer{}
	svc := NewService(repo).WithMailer(mailer, "support@keluargamart.id").WithSyncEmail()

	ctx := context.Background()
	reporter := &auth.User{ID: 10, Name: "Andi Reporter", Email: "andi@keluargamart.id", Role: auth.RoleEmployee}
	admin := &auth.User{
		ID:    99,
		Name:  "Admin IT",
		Email: "admin@keluargamart.id",
		Role:  auth.RoleAdmin,
	}

	ticket, err := svc.Create(ctx, reporter, CreateTicketRequest{
		Title:       "Printer Resi Rusak",
		Description: "Kabel USB kendur",
		Category:    "pos",
	})
	if err != nil {
		t.Fatalf("create failed: %v", err)
	}

	// Reset sent emails from creation
	mailer.sent = nil

	// 1. Reporter replies to ticket -> sends copy to support@keluargamart.id
	_, err = svc.AddComment(ctx, reporter, ticket.ID, CreateCommentRequest{
		Comment: "Sudah saya coba pasang ulang kabelnya tapi belum bisa.",
	})
	if err != nil {
		t.Fatalf("add comment failed: %v", err)
	}
	if len(mailer.sent) != 1 {
		t.Fatalf("expected 1 email to support, got %d", len(mailer.sent))
	}
	if mailer.sent[0].to != "support@keluargamart.id" {
		t.Fatalf("expected recipient support@keluargamart.id, got %s", mailer.sent[0].to)
	}
	if !strings.Contains(mailer.sent[0].body, "Andi Reporter") {
		t.Fatalf("expected commenter in body, got: %s", mailer.sent[0].body)
	}

	mailer.sent = nil

	// 2. Admin adds internal note -> MUST NOT dispatch to reporter
	_, err = svc.AddComment(ctx, admin, ticket.ID, CreateCommentRequest{
		Comment:    "Catatan teknisi: sudah dikirim kabel pengganti.",
		IsInternal: true,
	})
	if err != nil {
		t.Fatalf("add internal comment failed: %v", err)
	}
	if len(mailer.sent) != 0 {
		t.Fatalf("expected 0 emails for internal comment, got %d", len(mailer.sent))
	}

	mailer.sent = nil

	// 3. Admin replies publicly to ticket -> sends copy to reporter (andi@keluargamart.id)
	_, err = svc.AddComment(ctx, admin, ticket.ID, CreateCommentRequest{
		Comment:    "Kabel pengganti sudah dalam perjalanan melalui kurir internal.",
		IsInternal: false,
	})
	if err != nil {
		t.Fatalf("add public comment failed: %v", err)
	}
	if len(mailer.sent) != 1 {
		t.Fatalf("expected 1 email to reporter, got %d", len(mailer.sent))
	}
	if mailer.sent[0].to != "andi@keluargamart.id" {
		t.Fatalf("expected recipient andi@keluargamart.id, got %s", mailer.sent[0].to)
	}
	if !strings.Contains(mailer.sent[0].body, "Kabel pengganti sudah dalam perjalanan") {
		t.Fatalf("expected comment text in body, got: %s", mailer.sent[0].body)
	}
}

func TestService_UpdateStatus_EmailDispatch(t *testing.T) {
	repo := newMockRepo()
	mailer := &mockMailer{}
	svc := NewService(repo).WithMailer(mailer, "support@keluargamart.id").WithSyncEmail()

	ctx := context.Background()
	reporter := &auth.User{ID: 15, Name: "Dewi Kasir", Email: "dewi@keluargamart.id", Role: auth.RoleCashier}
	admin := &auth.User{
		ID:    99,
		Name:  "Admin IT",
		Email: "admin@keluargamart.id",
		Role:  auth.RoleAdmin,
	}

	ticket, err := svc.Create(ctx, reporter, CreateTicketRequest{
		Title:       "Error Sinkronisasi Stok",
		Description: "Stok gula pasir tidak sinkron",
		Category:    "inventory",
	})
	if err != nil {
		t.Fatalf("create failed: %v", err)
	}

	mailer.sent = nil

	// Update status to resolved
	newStatus := "resolved"
	_, err = svc.Update(ctx, admin, ticket.ID, UpdateTicketRequest{
		Status: &newStatus,
	})
	if err != nil {
		t.Fatalf("update failed: %v", err)
	}

	if len(mailer.sent) != 1 {
		t.Fatalf("expected 1 status update email, got %d", len(mailer.sent))
	}
	if mailer.sent[0].to != "dewi@keluargamart.id" {
		t.Fatalf("expected recipient dewi@keluargamart.id, got %s", mailer.sent[0].to)
	}
	if !strings.Contains(mailer.sent[0].subject, "RESOLVED") {
		t.Fatalf("expected subject to contain RESOLVED, got: %s", mailer.sent[0].subject)
	}
}

func TestService_BuyerTenantIsolation(t *testing.T) {
	repo := newMockRepo()
	svc := NewService(repo)
	ctx := context.Background()

	buyerA := &auth.User{ID: 101, Name: "Buyer A", Role: auth.RoleBuyer, Permissions: auth.Permissions{"support.tickets.create": true, "support.tickets.view": true}}
	buyerB := &auth.User{ID: 102, Name: "Buyer B", Role: auth.RoleBuyer, Permissions: auth.Permissions{"support.tickets.create": true, "support.tickets.view": true}}

	ticketA, err := svc.Create(ctx, buyerA, CreateTicketRequest{
		Title:       "Tiket Rahasia Buyer A",
		Description: "Data sensitif",
		Category:    "pemesanan & faktur", // tests category normalization as well
	})
	if err != nil {
		t.Fatalf("create failed: %v", err)
	}
	if ticketA.Category != "billing" {
		t.Fatalf("expected category normalized to billing, got %s", ticketA.Category)
	}

	// Buyer B attempts to Get ticket A -> must be rejected
	_, _, err = svc.Get(ctx, buyerB, ticketA.ID)
	if err != ErrUnauthorized {
		t.Fatalf("expected ErrUnauthorized for Buyer B accessing Ticket A, got %v", err)
	}

	// Buyer A can get their own ticket
	_, _, err = svc.Get(ctx, buyerA, ticketA.ID)
	if err != nil {
		t.Fatalf("Buyer A should access their own ticket: %v", err)
	}
}
