package dmarcrpt

import (
	"os"
	"reflect"
	"strings"
	"testing"
)

const reportExample = `<?xml version="1.0" encoding="UTF-8" ?>
<feedback>
  <report_metadata>
    <org_name>google.com</org_name>
    <email>noreply-dmarc-support@google.com</email>
    <extra_contact_info>https://support.google.com/a/answer/2466580</extra_contact_info>
    <report_id>10051505501689795560</report_id>
    <date_range>
      <begin>1596412800</begin>
      <end>1596499199</end>
    </date_range>
  </report_metadata>
  <policy_published>
    <domain>example.org</domain>
    <adkim>r</adkim>
    <aspf>r</aspf>
    <p>reject</p>
    <sp>reject</sp>
    <pct>100</pct>
  </policy_published>
  <record>
    <row>
      <source_ip>127.0.0.1</source_ip>
      <count>1</count>
      <policy_evaluated>
        <disposition>none</disposition>
        <dkim>pass</dkim>
        <spf>pass</spf>
      </policy_evaluated>
    </row>
    <identifiers>
      <header_from>example.org</header_from>
    </identifiers>
    <auth_results>
      <dkim>
        <domain>example.org</domain>
        <result>pass</result>
        <selector>example</selector>
      </dkim>
      <spf>
        <domain>example.org</domain>
        <result>pass</result>
      </spf>
    </auth_results>
  </record>
</feedback>
`

func TestParseReport(t *testing.T) {
	var expect = &Feedback{
		ReportMetadata: ReportMetadata{
			OrgName:          "google.com",
			Email:            "noreply-dmarc-support@google.com",
			ExtraContactInfo: "https://support.google.com/a/answer/2466580",
			ReportID:         "10051505501689795560",
			DateRange: DateRange{
				Begin: 1596412800,
				End:   1596499199,
			},
		},
		PolicyPublished: PolicyPublished{
			Domain:          "example.org",
			ADKIM:           "r",
			ASPF:            "r",
			Policy:          "reject",
			SubdomainPolicy: "reject",
			Percentage:      100,
		},
		Records: []ReportRecord{
			{
				Row: Row{
					SourceIP: "127.0.0.1",
					Count:    1,
					PolicyEvaluated: PolicyEvaluated{
						Disposition: DispositionNone,
						DKIM:        DMARCPass,
						SPF:         DMARCPass,
					},
				},
				Identifiers: Identifiers{
					HeaderFrom: "example.org",
				},
				AuthResults: AuthResults{
					DKIM: []DKIMAuthResult{
						{
							Domain:   "example.org",
							Result:   DKIMPass,
							Selector: "example",
						},
					},
					SPF: []SPFAuthResult{
						{
							Domain: "example.org",
							Result: SPFPass,
						},
					},
				},
			},
		},
	}

	feedback, err := ParseReport(strings.NewReader(reportExample))
	if err != nil {
		t.Fatalf("parsing report: %s", err)
	}
	if !reflect.DeepEqual(expect, feedback) {
		t.Fatalf("expected:\n%#v\ngot:\n%#v", expect, feedback)
	}
}

func TestParseMessageReport(t *testing.T) {
	const dir = "../testdata/dmarc-reports"
	files, err := os.ReadDir(dir)
	if err != nil {
		t.Fatalf("listing dmarc report emails: %s", err)
	}

	for _, file := range files {
		p := dir + "/" + file.Name()
		f, err := os.Open(p)
		if err != nil {
			t.Fatalf("open %q: %s", p, err)
		}
		_, err = ParseMessageReport(f)
		if err != nil {
			t.Fatalf("ParseMessageReport: %q: %s", p, err)
		}
		f.Close()
	}

	// No report in a non-multipart message.
	_, err = ParseMessageReport(strings.NewReader("From: <mjl@mox.example>\r\n\r\nNo report.\r\n"))
	if err != ErrNoReport {
		t.Fatalf("message without report, got err %#v, expected ErrNoreport", err)
	}

	// No report in a multipart message.
	var multipartNoreport = strings.ReplaceAll(`From: <mjl@mox.example>
To: <mjl@mox.example>
Subject: Report Domain: mox.example Submitter: mail.mox.example
MIME-Version: 1.0
Content-Type: multipart/alternative; boundary="===============5735553800636657282=="

--===============5735553800636657282==
Content-Type: text/plain
MIME-Version: 1.0

test

--===============5735553800636657282==
Content-Type: text/html
MIME-Version: 1.0

<html></html>

--===============5735553800636657282==--
`, "\n", "\r\n")
	_, err = ParseMessageReport(strings.NewReader(multipartNoreport))
	if err != ErrNoReport {
		t.Fatalf("message without report, got err %#v, expected ErrNoreport", err)
	}
}

func FuzzParseReport(f *testing.F) {
	f.Add("")
	f.Add(reportExample)
	f.Fuzz(func(t *testing.T, s string) {
		ParseReport(strings.NewReader(s))
	})
}
