package repository

import (
	"errors"
	"net/http"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/storage/repository"
)

// declare conformance with repository types
var _ repository.Repository = (*HTTPRepository)(nil)

type remoteFile struct {
	*http.Response
	uri fyne.URI
}

func (f *remoteFile) Close() error {
	if f.Response == nil {
		return nil
	}
	return f.Response.Body.Close()
}

func (f *remoteFile) Read(p []byte) (int, error) {
	if f.Response == nil {
		return 0, nil
	}
	return f.Response.Body.Read(p)
}

func (f *remoteFile) URI() fyne.URI {
	return f.uri
}

// HTTPRepository implements a proxy for interacting with remote resources
// using golang's net/http library.
//
// This repository is suitable to handle the http:// and https:// scheme.
//
// Since: 2.1
type HTTPRepository struct{}

// NewHTTPRepository creates a new HTTPRepository instance.
// The caller needs to call repository.Register() with the result of this function.
//
// Since: 2.1
func NewHTTPRepository() *HTTPRepository {
	return &HTTPRepository{}
}

func constructURI(u fyne.URI) string {
	uri := ""
	uri += u.Scheme() + "://"
	uri += u.Authority()
	if u.Path() != "" {
		uri += u.Path()
	}
	if u.Query() != "" {
		uri += "?" + u.Query()
	}
	if u.Fragment() != "" {
		uri += "#" + u.Fragment()
	}

	return uri
}

// Exists checks whether the resource at u returns a
// non "404 NOT FOUND" response header.
//
// Implements: repository.Repository
//
// Since: 2.1
func (r *HTTPRepository) Exists(u fyne.URI) (bool, error) {
	uri := constructURI(u)
	resp, err := http.Head(uri)
	if err != nil {
		return false, err
	}
	if resp.StatusCode == http.StatusNotFound {
		return false, nil
	}

	return true, nil
}

// Reader provides a interface for reading the body of the response received
// from the request to u.
//
// Implements: repository.Repository
//
// Since: 2.1
func (r *HTTPRepository) Reader(u fyne.URI) (fyne.URIReadCloser, error) {
	uri := constructURI(u)
	resp, err := http.Get(uri)

	return &remoteFile{Response: resp, uri: u}, err
}

// CanRead makes a HEAD HTTP request to analyse the headers received
// from the remote server.
// Any response status code apart from 2xx is considered to be invalid.
//
// Implements: repository.Repository
//
// Since: 2.1
func (r *HTTPRepository) CanRead(u fyne.URI) (bool, error) {
	uri := constructURI(u)
	resp, err := http.Head(uri)
	if err != nil {
		return false, err
	}
	if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusIMUsed {
		return false, errors.New("remote server did not return a successful response")
	}

	return true, nil
}

// Destroy satisfies the repository.Repository interface.
//
// Implements: repository.Repository
//
// Since: 2.1
func (r *HTTPRepository) Destroy(string) {
	// do nothing
}
