<commit_before>package bksv

// Better regex for caller code
// Better language for promoting caller code
// Flag for 'first ever' complaint ?
// device.UUIDs ... use Guy's lib, and submit.
// IDs for complainants, and complaints
// Cutover: 1st May.
// Share the published report URLs with bert et al.

// Package for posting a {ComplainerProfile,Complaint} to BKSV's web form

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"time"

	"github.com/skypies/util/date"
	
	"github.com/skypies/complaints/complaintdb/types"
	"github.com/skypies/complaints/config"
)

//https://viewpoint.emsbk.com/<sitename>?response=json
const bksvHost = "viewpoint.emsbk.com"
const bksvPath = "/sfo5" + "?response=json" // response *must* be a GET param, not POST

// {{{ PopulateForm

func PopulateForm(c types.Complaint, submitkey string) url.Values {
	first,last := c.Profile.SplitName()
	addr := c.Profile.GetStructuredAddress()
	if c.Activity == "" { c.Activity = "Loud noise" }

	vals := url.Values{
		"response":         {"json"}, // Must always set this as a GET param
		"contactmethod":    {"App"},
		"apiKey":           {config.Get("bksv.apiKey")},
		
		"caller_code":      {c.Profile.CallerCode},
		"name":             {first},
		"surname":          {last},
		"address1":         {addr.Number + " " + addr.Street},
		"address2":         {""},
		"zipcode":          {addr.Zip},
		"city":             {addr.City},
		"state":            {addr.State},
		"email":            {c.Profile.EmailAddress},

		"airports":         {"KSFO"},  // KOAK, KSJC, KSAN
		"month":            {date.InPdt(c.Timestamp).Format("1")},
		"day":              {date.InPdt(c.Timestamp).Format("2")},
		"year":             {date.InPdt(c.Timestamp).Format("2006")},
		"hour":             {date.InPdt(c.Timestamp).Format("15")},
		"min":              {date.InPdt(c.Timestamp).Format("4")},
		
		"aircraftcategory": {"J"},
		"eventtype":        {"Loud noise"}, // perhaps map c.Activity to something ?
		"comments":         {c.Description},
		"responserequired": {"N"},
		"enquirytype":      {"C"},

		"submit":           {"Submit complaint"},
		//"submitkey":        {submitkey},

		"nowebtrak": {"1"},
		"defaulttime": {"0"},
		"webtraklinkback": {""},
		"title": {""},
		"homephone": {""},
		"workphone": {""},
		"cellphone": {""},

		"browser_name":     {c.Browser.Name},
		"browser_version":  {c.Browser.Version},
		"browser_vendor":   {c.Browser.Vendor},
		"browser_uuid":     {c.Browser.UUID},
		"browser_platform": {c.Browser.Platform},
	}

	if c.AircraftOverhead.FlightNumber != "" {
		vals.Add("acid", c.AircraftOverhead.Callsign)
		vals.Add("aacode", c.AircraftOverhead.Id2)
		vals.Add("tailnumber", c.AircraftOverhead.Registration)
		//vals.Add("aircrafttype", c.AircraftOverhead.EquipType) // This was confusing the server

		//vals.Add("adflag", "??") // Operation type (A, D or O for Arr, Dept or Overflight)
		//vals.Add("beacon", "??") // Squawk SSR code (eg 2100)
	}

	return vals
}

// }}}
// {{{ PostComplaint

// https://complaints-staging.bksv.com/sfo2?json=1&resp=json
// {"result":"1",
//  "title":"Complaint Received",
//  "body":"Thank you. We have received your complaint."}

func PostComplaint(client *http.Client, c types.Complaint) (*types.Submission, error) {
	// Initialize a new submission object, inheriting from previous
	s := types.Submission{
		Attempts:  c.Submission.Attempts + 1,
		Log:       c.Submission.Log+fmt.Sprintf("\n--------=={ PostComplaint @ %s }==-\n", time.Now()),
		Key:       c.Submission.Key, // We're now keyless, should prob strip this out
		T:         time.Now().UTC(),
		Outcome:   types.SubmissionFailed, // Be pessimistic right up until the end
	}

	// We used to have to fetch a unique key (which lived in the form),
	// that we'd need to submit with the rest of the complaint; that
	// prevented dupes on their end. But the new version skips that
	// requirement for API based submissions like ours, so we're
	// keyless now.
	s.Log += fmt.Sprintf("----{ time: %s }----\n  --{ keyless submission }--\n", s.T)

	vals := PopulateForm(c, "")
	s.Log += "Submitting these vals:-\n"
	for k,v := range vals { s.Log += fmt.Sprintf(" * %-20.20s: %v\n", k, v) }
	s.Log += "\n"

	//	resp,err := client.PostForm("https://"+bksvHost+bksvPath, vals)
	req,_ := http.NewRequest("POST", "https://"+bksvHost+bksvPath, strings.NewReader(vals.Encode()))
	reqBytes,_ := httputil.DumpRequestOut(req,true)
	s.Log += "Full req:-\n--\n"+string(reqBytes)+"\n--\n\n"
	resp,err := client.Do(req)
	s.D = time.Since(s.T)
	if err != nil {
		if strings.Contains(err.Error(), "DEADLINE_EXCEEDED") {
			s.Outcome = types.SubmissionTimeout
		}
		s.Log += fmt.Sprintf("ComplaintPOST: Posting error (dur=%s): %v\n", s.D, err)
		return &s,err
	}

	defer resp.Body.Close()
	body,_ := ioutil.ReadAll(resp.Body)
	s.Log += fmt.Sprintf("ComplaintPOST: HTTP response '%s'\n", resp.Status)
	s.Response = []byte(body)
	if resp.StatusCode >= 400 {
		s.Log += fmt.Sprintf("ComplaintPOST: HTTP Body:-\n%s\n--\n", body)
		return &s,fmt.Errorf("ComplaintPOST: HTTP err %s", resp.Status)
	}

	var jsonMap map[string]interface{}
	if err := json.Unmarshal([]byte(body), &jsonMap); err != nil {
		s.Log += fmt.Sprintf("ComplaintPOST: JSON unmarshal '%v'\nBody:-\n%s\n--\n", err, body)
		return &s,fmt.Errorf("ComplaintPOST: JSON unmarshal %v", err)
		/* A few times, the remote site failed to send JSON responses, and sent HTML instead. This
     * will work in that case.
			if !regexp.MustCompile(`(?i:received your complaint)`).MatchString(string(body)) {
				debug += fmt.Sprintf("BKSV body ...\n%s\n------\n", string(body))
				return debug,fmt.Errorf("Returned response did not say 'received your complaint'")
			} else {<commit_msg>Bah, this header vanishes<commit_after>package bksv

// Better regex for caller code
// Better language for promoting caller code
// Flag for 'first ever' complaint ?
// device.UUIDs ... use Guy's lib, and submit.
// IDs for complainants, and complaints
// Cutover: 1st May.
// Share the published report URLs with bert et al.

// Package for posting a {ComplainerProfile,Complaint} to BKSV's web form

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"time"

	"github.com/skypies/util/date"
	
	"github.com/skypies/complaints/complaintdb/types"
	"github.com/skypies/complaints/config"
)

//https://viewpoint.emsbk.com/<sitename>?response=json
const bksvHost = "viewpoint.emsbk.com"
const bksvPath = "/sfo5" + "?response=json" // response *must* be a GET param, not POST

// {{{ PopulateForm

func PopulateForm(c types.Complaint, submitkey string) url.Values {
	first,last := c.Profile.SplitName()
	addr := c.Profile.GetStructuredAddress()
	if c.Activity == "" { c.Activity = "Loud noise" }

	vals := url.Values{
		"response":         {"json"}, // Must always set this as a GET param
		"contactmethod":    {"App"},
		"apiKey":           {config.Get("bksv.apiKey")},
		
		"caller_code":      {c.Profile.CallerCode},
		"name":             {first},
		"surname":          {last},
		"address1":         {addr.Number + " " + addr.Street},
		"address2":         {""},
		"zipcode":          {addr.Zip},
		"city":             {addr.City},
		"state":            {addr.State},
		"email":            {c.Profile.EmailAddress},

		"airports":         {"KSFO"},  // KOAK, KSJC, KSAN
		"month":            {date.InPdt(c.Timestamp).Format("1")},
		"day":              {date.InPdt(c.Timestamp).Format("2")},
		"year":             {date.InPdt(c.Timestamp).Format("2006")},
		"hour":             {date.InPdt(c.Timestamp).Format("15")},
		"min":              {date.InPdt(c.Timestamp).Format("4")},
		
		"aircraftcategory": {"J"},
		"eventtype":        {"Loud noise"}, // perhaps map c.Activity to something ?
		"comments":         {c.Description},
		"responserequired": {"N"},
		"enquirytype":      {"C"},

		"submit":           {"Submit complaint"},
		//"submitkey":        {submitkey},

		"nowebtrak": {"1"},
		"defaulttime": {"0"},
		"webtraklinkback": {""},
		"title": {""},
		"homephone": {""},
		"workphone": {""},
		"cellphone": {""},

		"browser_name":     {c.Browser.Name},
		"browser_version":  {c.Browser.Version},
		"browser_vendor":   {c.Browser.Vendor},
		"browser_uuid":     {c.Browser.UUID},
		"browser_platform": {c.Browser.Platform},
	}

	if c.AircraftOverhead.FlightNumber != "" {
		vals.Add("acid", c.AircraftOverhead.Callsign)
		vals.Add("aacode", c.AircraftOverhead.Id2)
		vals.Add("tailnumber", c.AircraftOverhead.Registration)
		//vals.Add("aircrafttype", c.AircraftOverhead.EquipType) // This was confusing the server

		//vals.Add("adflag", "??") // Operation type (A, D or O for Arr, Dept or Overflight)
		//vals.Add("beacon", "??") // Squawk SSR code (eg 2100)
	}

	return vals
}

// }}}
// {{{ PostComplaint

// https://complaints-staging.bksv.com/sfo2?json=1&resp=json
// {"result":"1",
//  "title":"Complaint Received",
//  "body":"Thank you. We have received your complaint."}

func PostComplaint(client *http.Client, c types.Complaint) (*types.Submission, error) {
	// Initialize a new submission object, inheriting from previous
	s := types.Submission{
		Attempts:  c.Submission.Attempts + 1,
		Log:       c.Submission.Log+fmt.Sprintf("\n--------=={ PostComplaint @ %s }==-\n", time.Now()),
		Key:       c.Submission.Key, // We're now keyless, should prob strip this out
		T:         time.Now().UTC(),
		Outcome:   types.SubmissionFailed, // Be pessimistic right up until the end
	}

	// We used to have to fetch a unique key (which lived in the form),
	// that we'd need to submit with the rest of the complaint; that
	// prevented dupes on their end. But the new version skips that
	// requirement for API based submissions like ours, so we're
	// keyless now.
	s.Log += fmt.Sprintf("----{ time: %s }----\n  --{ keyless submission }--\n", s.T)

	vals := PopulateForm(c, "")
	s.Log += "Submitting these vals:-\n"
	for k,v := range vals { s.Log += fmt.Sprintf(" * %-20.20s: %v\n", k, v) }
	s.Log += "\n"

	// resp,err := client.PostForm("https://"+bksvHost+bksvPath, vals)
	req,_ := http.NewRequest("POST", "https://"+bksvHost+bksvPath, strings.NewReader(vals.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	reqBytes,_ := httputil.DumpRequestOut(req,true)
	s.Log += "Full req:-\n--\n"+string(reqBytes)+"\n--\n\n"
	resp,err := client.Do(req)

	s.D = time.Since(s.T)
	if err != nil {
		if strings.Contains(err.Error(), "DEADLINE_EXCEEDED") {
			s.Outcome = types.SubmissionTimeout
		}
		s.Log += fmt.Sprintf("ComplaintPOST: Posting error (dur=%s): %v\n", s.D, err)
		return &s,err
	}

	defer resp.Body.Close()
	body,_ := ioutil.ReadAll(resp.Body)
	s.Log += fmt.Sprintf("ComplaintPOST: HTTP response '%s'\n", resp.Status)
	s.Response = []byte(body)
	if resp.StatusCode >= 400 {
		s.Log += fmt.Sprintf("ComplaintPOST: HTTP Body:-\n%s\n--\n", body)
		return &s,fmt.Errorf("ComplaintPOST: HTTP err %s", resp.Status)
	}

	var jsonMap map[string]interface{}
	if err := json.Unmarshal([]byte(body), &jsonMap); err != nil {
		s.Log += fmt.Sprintf("ComplaintPOST: JSON unmarshal '%v'\nBody:-\n%s\n--\n", err, body)
		return &s,fmt.Errorf("ComplaintPOST: JSON unmarshal %v", err)
		/* A few times, the remote site failed to send JSON responses, and sent HTML instead. This
     * will work in that case.
			if !regexp.MustCompile(`(?i:received your complaint)`).MatchString(string(body)) {
				debug += fmt.Sprintf("BKSV body ...\n%s\n------\n", string(body))
				return debug,fmt.Errorf("Returned response did not say 'received your complaint'")
			} else {
{'fraction_non_alphanumeric': 0.18826469067723592, 'fraction_numerical': 0.005342064449422712, 'mean_word_length': 3.2222626409603494, 'pattern_counts': {'":': 80, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 8, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4279624', 'n_tokens_mistral': 4403, 'n_tokens_neox': 4076, 'n_words': 953}