/*
 Copyright 2013-2015 Canonical Ltd.

 This program is free software: you can redistribute it and/or modify it
 under the terms of the GNU General Public License version 3, as published
 by the Free Software Foundation.

 This program is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranties of
 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
 PURPOSE.  See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along
 with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// webchecker checks whether we're actually connected by doing an http
// GET to the Ubuntu connectivity check URL,
// http://start.ubuntu.com/connectivity-check.html
//
// We could make it be https to make extra doubly sure, but it's expensive
// overkill for the majority of cases.
package connectivity

import (
	"bytes"
	"crypto/md5"
	"crypto/tls"
	"fmt"
	"gitlab.com/ubports/development/core/lomiri-push-service/logger"
	"io"
	"net/http"
	"time"
)

const ResponseLimit = 1024

// how much web would a webchecker check

type Webchecker interface {
	// Webcheck checks whether retrieving the URL works, and if its
	// contents match the target. If so, then it sends true; if anything
	// fails, it sends false.
	Webcheck(chan<- bool)
	// Close idle connections.
	Close()
}

type webchecker struct {
	log       logger.Logger
	url       string
	targetMD5 string
	targetMatch string
	cli       *http.Client
}

// Build a webchecker for the given URL, that should match the target MD5.
func NewWebchecker(url, targetMD5, targetMatch string, timeout time.Duration, log logger.Logger) Webchecker {
	cli := &http.Client{
		Timeout:   timeout,
		Transport: &http.Transport{
			// ignore errors since the system clock might not be
			// set correctly on startup
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
			TLSHandshakeTimeout: timeout,
		},
	}
	return &webchecker{log, url, targetMD5, targetMatch, cli}
}

// ensure webchecker implements Webchecker
var _ Webchecker = &webchecker{}

func (wb *webchecker) Webcheck(ch chan<- bool) {
	response, err := wb.cli.Get(wb.url)
	if err != nil {
		wb.log.Errorf("while GETting %s: %v", wb.url, err)
		ch <- false
		return
	}
	defer response.Body.Close()

	hash := md5.New()
	var buf bytes.Buffer
	var w io.Writer
	if len(wb.targetMatch) > 0 {
		w = &buf
	} else {
		w = hash
	}
	_, err = io.CopyN(w, response.Body, ResponseLimit)
	if err != io.EOF {
		if err == nil {
			wb.log.Errorf("reading %s, but response body is larger than 1k.", wb.url)
		} else {
			wb.log.Errorf("reading %s, expecting EOF, got: %v", wb.url, err)
		}
		ch <- false
		return
	}
	if len(wb.targetMatch) > 0 {
		if bytes.Contains(buf.Bytes(), []byte(wb.targetMatch)) {
			wb.log.Debugf("connectivity check passed.")
			ch <- true
		} else {
			wb.log.Debugf("connectivity check failed: string not found.")
			ch <- false
		}
		return
	}
	sum := fmt.Sprintf("%x", hash.Sum(nil))
	if sum == wb.targetMD5 {
		wb.log.Debugf("connectivity check passed.")
		ch <- true
	} else {
		wb.log.Debugf("connectivity check failed: content mismatch.")
		ch <- false
	}
}

func (wb *webchecker) Close() {
	wb.cli.Transport.(*http.Transport).CloseIdleConnections()
}
