package events

import (
	"testing"

	"launchpad.net/email-reminder/internal/config"
	"launchpad.net/email-reminder/internal/util"
)

func TestUserToEmailRecipient(t *testing.T) {
	tests := []struct {
		name string
		user User
		want util.EmailRecipient
	}{
		{
			name: "user with all fields populated",
			user: User{
				FirstName: "John",
				LastName:  "Doe",
				Email:     "john@example.com",
			},
			want: util.EmailRecipient{
				Email:     "john@example.com",
				FirstName: "John",
				LastName:  "Doe",
			},
		},
		{
			name: "user with email only",
			user: User{
				FirstName: "",
				LastName:  "",
				Email:     "john@example.com",
			},
			want: util.EmailRecipient{
				Email:     "john@example.com",
				FirstName: "",
				LastName:  "",
			},
		},
		{
			name: "user with first name only",
			user: User{
				FirstName: "John",
				LastName:  "",
				Email:     "john@example.com",
			},
			want: util.EmailRecipient{
				Email:     "john@example.com",
				FirstName: "John",
				LastName:  "",
			},
		},
		{
			name: "user with last name only",
			user: User{
				FirstName: "",
				LastName:  "Doe",
				Email:     "john@example.com",
			},
			want: util.EmailRecipient{
				Email:     "john@example.com",
				FirstName: "",
				LastName:  "Doe",
			},
		},
		{
			name: "user with whitespace in all fields",
			user: User{
				FirstName: "  John  ",
				LastName:  "  Doe  ",
				Email:     "  john@example.com  ",
			},
			want: util.EmailRecipient{
				Email:     "john@example.com",
				FirstName: "John",
				LastName:  "Doe",
			},
		},
		{
			name: "user with whitespace-only first name",
			user: User{
				FirstName: "   ",
				LastName:  "Doe",
				Email:     "john@example.com",
			},
			want: util.EmailRecipient{
				Email:     "john@example.com",
				FirstName: "",
				LastName:  "Doe",
			},
		},
		{
			name: "user with whitespace-only last name",
			user: User{
				FirstName: "John",
				LastName:  "   ",
				Email:     "john@example.com",
			},
			want: util.EmailRecipient{
				Email:     "john@example.com",
				FirstName: "John",
				LastName:  "",
			},
		},
		{
			name: "user with whitespace-only email",
			user: User{
				FirstName: "John",
				LastName:  "Doe",
				Email:     "   ",
			},
			want: util.EmailRecipient{
				Email:     "",
				FirstName: "John",
				LastName:  "Doe",
			},
		},
		{
			name: "user with invalid email",
			user: User{
				FirstName: "John",
				LastName:  "Doe",
				Email:     "john@",
			},
			want: util.EmailRecipient{
				Email:     "",
				FirstName: "John",
				LastName:  "Doe",
			},
		},
		{
			name: "user with tabs and newlines",
			user: User{
				FirstName: "\t\nJohn\n\t",
				LastName:  "\tDoe\n",
				Email:     "\njohn@example.com\t",
			},
			want: util.EmailRecipient{
				Email:     "john@example.com",
				FirstName: "John",
				LastName:  "Doe",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := tt.user.ToEmailRecipient()
			if got.Email != tt.want.Email {
				t.Errorf("ToEmailRecipient().Email = %q, want %q", got.Email, tt.want.Email)
			}
			if got.FirstName != tt.want.FirstName {
				t.Errorf("ToEmailRecipient().FirstName = %q, want %q", got.FirstName, tt.want.FirstName)
			}
			if got.LastName != tt.want.LastName {
				t.Errorf("ToEmailRecipient().LastName = %q, want %q", got.LastName, tt.want.LastName)
			}
		})
	}
}

func TestLoadUserEvents(t *testing.T) {
	tests := []struct {
		name     string
		filename string
		contents []byte
		wantErr  bool
		validate func(*testing.T, User)
	}{
		{
			name:     "valid XML with all fields",
			filename: "test.xml",
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<email-reminder_user>
  <first_name>John</first_name>
  <last_name>Doe</last_name>
  <email>john@example.com</email>
  <events>
    <event type="birthday">
      <name>Alice's Birthday</name>
      <date>2025-03-15</date>
    </event>
  </events>
</email-reminder_user>`),
			wantErr: false,
			validate: func(t *testing.T, u User) {
				if u.FirstName != "John" {
					t.Errorf("FirstName = %q, want %q", u.FirstName, "John")
				}
				if u.LastName != "Doe" {
					t.Errorf("LastName = %q, want %q", u.LastName, "Doe")
				}
				if u.Email != "john@example.com" {
					t.Errorf("Email = %q, want %q", u.Email, "john@example.com")
				}
				if len(u.Events) != 1 {
					t.Errorf("Events length = %d, want %d", len(u.Events), 1)
				}
			},
		},
		{
			name:     "valid XML with minimal fields",
			filename: "minimal.xml",
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<email-reminder_user>
  <email>user@example.com</email>
</email-reminder_user>`),
			wantErr: false,
			validate: func(t *testing.T, u User) {
				if u.FirstName != "" {
					t.Errorf("FirstName = %q, want empty", u.FirstName)
				}
				if u.LastName != "" {
					t.Errorf("LastName = %q, want empty", u.LastName)
				}
				if u.Email != "user@example.com" {
					t.Errorf("Email = %q, want %q", u.Email, "user@example.com")
				}
				if len(u.Events) != 0 {
					t.Errorf("Events length = %d, want %d", len(u.Events), 0)
				}
			},
		},
		{
			name:     "invalid XML - malformed",
			filename: "invalid.xml",
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?><email-reminder_user><email>test@example.com</email`),
			wantErr:  true,
		},
		{
			name:     "invalid XML - missing closing tag",
			filename: "invalid2.xml",
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?><email-reminder_user><email>test@example.com</email>`),
			wantErr:  true,
		},
		{
			name:     "invalid XML - not well-formed",
			filename: "invalid3.xml",
			contents: []byte(`not xml at all`),
			wantErr:  true,
		},
		{
			name:     "Billion Laughs attack - nested entity expansion",
			filename: "billion-laughs.xml",
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE email-reminder_user [
  <!ENTITY lol "lol">
  <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
  <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
  <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
  <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
  <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
  <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<email-reminder_user>
  <email>&lol9;</email>
</email-reminder_user>`),
			wantErr: true,
		},
		{
			name:     "XML bomb - large entity reference",
			filename: "xml-bomb.xml",
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE email-reminder_user [
  <!ENTITY bomb "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA">
]>
<email-reminder_user>
  <email>&bomb;&bomb;&bomb;&bomb;&bomb;&bomb;&bomb;&bomb;&bomb;&bomb;</email>
</email-reminder_user>`),
			wantErr: true,
		},
		{
			name:     "Quadratic blowup attack",
			filename: "quadratic.xml",
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE email-reminder_user [
  <!ENTITY a "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">
]>
<email-reminder_user>
  <email>&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;</email>
</email-reminder_user>`),
			wantErr: true,
		},
		{
			name:     "empty XML file",
			filename: "empty.xml",
			contents: []byte(``),
			wantErr:  true,
		},
		{
			name:     "valid XML with special characters",
			filename: "special.xml",
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<email-reminder_user>
  <first_name>José</first_name>
  <last_name>O'Brien-Müller</last_name>
  <email>josé@example.com</email>
</email-reminder_user>`),
			wantErr: false,
			validate: func(t *testing.T, u User) {
				if u.FirstName != "José" {
					t.Errorf("FirstName = %q, want %q", u.FirstName, "José")
				}
				if u.LastName != "O'Brien-Müller" {
					t.Errorf("LastName = %q, want %q", u.LastName, "O'Brien-Müller")
				}
			},
		},
		{
			name:     "XML with Unicode noncharacters",
			filename: "nonchar.xml",
			contents: []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<email-reminder_user>\n  <first_name>Test\uFFFE</first_name>\n  <last_name>User\uFFFF</last_name>\n  <email>test@example.com</email>\n</email-reminder_user>"),
			wantErr:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			conf := config.Config{
				MaxEventsPerUser:      10000,
				MaxRecipientsPerEvent: 200,
			}
			got, err := LoadUserEvents(tt.filename, tt.contents, conf)
			if tt.wantErr {
				if err == nil {
					t.Errorf("LoadUserEvents() error = nil, wantErr %v", tt.wantErr)
				}
				return
			}
			if err != nil {
				t.Errorf("LoadUserEvents() unexpected error = %v", err)
				return
			}
			if tt.validate != nil {
				tt.validate(t, got)
			}
		})
	}
}

func TestLoadUserEventsMaxLimit(t *testing.T) {
	tests := []struct {
		name      string
		filename  string
		contents  []byte
		maxEvents int
		wantErr   bool
	}{
		{
			name:      "within limit - 2 events with max 10",
			filename:  "test.xml",
			maxEvents: 10,
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<email-reminder_user>
  <email>test@example.com</email>
  <events>
    <event type="birthday">
      <name>Alice</name>
      <date>2025-03-15</date>
    </event>
    <event type="birthday">
      <name>Bob</name>
      <date>2025-04-20</date>
    </event>
  </events>
</email-reminder_user>`),
			wantErr: false,
		},
		{
			name:      "at limit - 2 events with max 2",
			filename:  "test.xml",
			maxEvents: 2,
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<email-reminder_user>
  <email>test@example.com</email>
  <events>
    <event type="birthday">
      <name>Alice</name>
      <date>2025-03-15</date>
    </event>
    <event type="birthday">
      <name>Bob</name>
      <date>2025-04-20</date>
    </event>
  </events>
</email-reminder_user>`),
			wantErr: false,
		},
		{
			name:      "exceeds limit - 3 events with max 2",
			filename:  "test.xml",
			maxEvents: 2,
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<email-reminder_user>
  <email>test@example.com</email>
  <events>
    <event type="birthday">
      <name>Alice</name>
      <date>2025-03-15</date>
    </event>
    <event type="birthday">
      <name>Bob</name>
      <date>2025-04-20</date>
    </event>
    <event type="birthday">
      <name>Charlie</name>
      <date>2025-05-10</date>
    </event>
  </events>
</email-reminder_user>`),
			wantErr: true,
		},
		{
			name:      "no events - within any limit",
			filename:  "test.xml",
			maxEvents: 1,
			contents: []byte(`<?xml version="1.0" encoding="UTF-8"?>
<email-reminder_user>
  <email>test@example.com</email>
</email-reminder_user>`),
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			conf := config.Config{
				MaxEventsPerUser:      tt.maxEvents,
				MaxRecipientsPerEvent: 200,
			}
			_, err := LoadUserEvents(tt.filename, tt.contents, conf)
			if tt.wantErr {
				if err == nil {
					t.Errorf("LoadUserEvents() error = nil, wantErr %v", tt.wantErr)
				}
			} else {
				if err != nil {
					t.Errorf("LoadUserEvents() unexpected error = %v", err)
				}
			}
		})
	}
}
