package client

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"

	"gogs.carducci-dante.gov.it/karmen/core/orm"
	"gogs.carducci-dante.gov.it/karmen/core/renderer"
)

// A client represents a client connection to the Headmaster test
// server.
type Client struct {
	Url      *url.URL
	Username string
	Password string
	User     string

	token string
}

// Dial connects to a test server instance at the specified address
// using the given credentials.
func Dial(host, username, password string) (*Client, error) {
	url, err := url.Parse(host)
	if err != nil {
		return nil, err
	}

	client := &Client{
		Url:      url,
		Username: username,
		Password: password,
	}

	response, err := client.SendRequest("GET", "get_token", nil)
	if err != nil {
		panic(err)
	}

	var data struct {
		Token string
		User  string
	}
	if err := json.Unmarshal(response, &data); err != nil {
		panic(err)
	}
	client.token = data.Token
	client.User = data.User

	return client, nil
}

func (c *Client) SendRequest(method string, path string, data []byte) ([]byte, error) {
	// Create the https request

	folderUrl, err := url.Parse(path)
	if err != nil {
		return nil, err
	}

	client := &http.Client{}
	req, err := http.NewRequest(method, c.Url.ResolveReference(folderUrl).String(), bytes.NewReader(data))
	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/json")
	req.SetBasicAuth(c.Username, c.Password)

	if c.token != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token))
	}

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return body, nil
}

func (c *Client) GetTeachers() ([]*orm.Teacher, error) {
	var (
		response renderer.JsonResponse
		teachers []*orm.Teacher
	)

	data, err := c.SendRequest("GET", "/api/teachers?format=json", nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &teachers); err != nil {
		return nil, err
	}
	return teachers, nil
}

func (c *Client) GetAdministratives() ([]*orm.Administrative, error) {
	var (
		response        renderer.JsonResponse
		administratives []*orm.Administrative
	)

	data, err := c.SendRequest("GET", "/api/administratives?format=json", nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &administratives); err != nil {
		return nil, err
	}
	return administratives, nil
}

func (c *Client) GetTeacher(id uint) (*orm.Teacher, error) {
	var (
		response renderer.JsonResponse
		teacher  *orm.Teacher
	)

	data, err := c.SendRequest("GET", fmt.Sprintf("/api/teachers/%d?format=json", id), nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &teacher); err != nil {
		return nil, err
	}
	return teacher, nil
}

func (c *Client) GetJob(id uint) (*orm.Job, error) {
	var (
		response renderer.JsonResponse
		job      *orm.Job
	)

	data, err := c.SendRequest("GET", fmt.Sprintf("/api/jobs/%d?format=json", id), nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &job); err != nil {
		return nil, err
	}
	return job, nil
}

func (c *Client) GetStudents() ([]*orm.Student, error) {
	var (
		response renderer.JsonResponse
		students []*orm.Student
	)

	data, err := c.SendRequest("GET", "/api/students?format=json", nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &students); err != nil {
		return nil, err
	}
	return students, nil
}

func (c *Client) GetSubjects() ([]*orm.Subject, error) {
	var (
		response renderer.JsonResponse
		subjects []*orm.Subject
	)

	data, err := c.SendRequest("GET", "/api/subjects?format=json", nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &subjects); err != nil {
		return nil, err
	}
	return subjects, nil
}

func (c *Client) AddSubject(subject *orm.Subject) error {
	var response renderer.JsonResponse

	data, err := json.Marshal(subject)
	if err != nil {
		return err
	}
	resp, err := c.SendRequest("POST", "/api/subjects/add/?format=json", data)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(resp, &response); err != nil {
		return err
	}
	if string(response.Error) != "" {
		return errors.New(string(response.Error))
	}
	return nil
}

func (c *Client) GetDepartments() ([]*orm.Department, error) {
	var (
		response    renderer.JsonResponse
		departments []*orm.Department
	)

	data, err := c.SendRequest("GET", "/api/departments?format=json", nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &departments); err != nil {
		return nil, err
	}
	return departments, nil
}

func (c *Client) GetOffices() ([]*orm.Office, error) {
	var (
		response renderer.JsonResponse
		offices  []*orm.Office
	)

	data, err := c.SendRequest("GET", "/api/offices?format=json", nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &offices); err != nil {
		return nil, err
	}
	return offices, nil
}

func (c *Client) GetClasses() ([]*orm.Class, error) {
	var (
		response renderer.JsonResponse
		classes  []*orm.Class
	)

	data, err := c.SendRequest("GET", "/api/classes?format=json", nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &classes); err != nil {
		return nil, err
	}
	return classes, nil
}

func (c *Client) GetClass(id uint) (*orm.Class, error) {
	var (
		response renderer.JsonResponse
		teacher  *orm.Class
	)

	data, err := c.SendRequest("GET", fmt.Sprintf("/api/classes/%d?format=json", id), nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &teacher); err != nil {
		return nil, err
	}
	return teacher, nil
}

func (c *Client) AddClass(class *orm.Class) error {
	var response renderer.JsonResponse

	data, err := json.Marshal(class)
	if err != nil {
		return err
	}
	resp, err := c.SendRequest("POST", "/api/classes/add/?format=json", data)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(resp, &response); err != nil {
		return err
	}
	if string(response.Error) != "" {
		return errors.New(string(response.Error))
	}
	return nil
}

func (c *Client) GetActivities() ([]*orm.Activity, error) {
	var (
		response   renderer.JsonResponse
		activities []*orm.Activity
	)

	data, err := c.SendRequest("GET", "/api/activities?format=json", nil)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(data, &response); err != nil {
		return nil, err
	}

	if string(response.Error) != "" {
		return nil, errors.New(string(response.Error))
	}

	if err := json.Unmarshal(response.Result, &activities); err != nil {
		return nil, err
	}
	return activities, nil
}

func (c *Client) AddStudent(student *orm.Student) (uint, error) {
	var response renderer.JsonResponse

	data, err := json.Marshal(student)
	if err != nil {
		return 0, err
	}
	resp, err := c.SendRequest("POST", "/api/students/add/?format=json", data)
	if err != nil {
		return 0, err
	}
	if err := json.Unmarshal(resp, &response); err != nil {
		return 0, err
	}
	if string(response.Error) != "" {
		return 0, errors.New(string(response.Error))
	}

	id, err := strconv.Atoi(string(response.Result))
	if err != nil {
		return 0, err
	}
	return uint(id), nil
}

func (c *Client) AddActivity(activity *orm.Activity) (uint, error) {
	var response renderer.JsonResponse
	data, err := json.Marshal(activity)
	if err != nil {
		return 0, err
	}
	resp, err := c.SendRequest("POST", "/api/activities/add/?format=json", data)
	if err != nil {
		return 0, err
	}

	if err := json.Unmarshal(resp, &response); err != nil {
		return 0, err
	}
	if string(response.Error) != "" {
		return 0, errors.New(string(response.Error))
	}

	id, err := strconv.Atoi(string(response.Result))
	if err != nil {
		return 0, err
	}

	return uint(id), nil
}

func (c *Client) DeleteActivity(activity *orm.Activity) (uint, error) {
	var response renderer.JsonResponse
	data, err := json.Marshal(activity)
	if err != nil {
		return 0, err
	}
	resp, err := c.SendRequest("DELETE", fmt.Sprintf("/api/activities/%d/delete?format=json", activity.ID), data)
	if err != nil {
		return 0, err
	}

	if err := json.Unmarshal(resp, &response); err != nil {
		return 0, err
	}
	if string(response.Error) != "" {
		return 0, errors.New(string(response.Error))
	}

	id, err := strconv.Atoi(string(response.Result))
	if err != nil {
		return 0, err
	}

	return uint(id), nil
}

func (c *Client) UpdateActivity(activity *orm.Activity) error {
	data, err := json.Marshal(activity)
	if err != nil {
		return err

	}
	_, err = c.SendRequest("POST", fmt.Sprintf("/api/activities/%d/update?format=json", activity.ID), data)
	if err != nil {
		return err
	}
	return nil
}

func (c *Client) UpdateStudent(student *orm.Student) error {
	data, err := json.Marshal(student)
	if err != nil {
		return err

	}
	_, err = c.SendRequest("POST", fmt.Sprintf("/api/students/%d/update?format=json", student.ID), data)
	if err != nil {
		return err
	}
	return nil
}

func (c *Client) UpdateTeacher(teacher *orm.Teacher) error {
	var response renderer.JsonResponse

	data, err := json.Marshal(teacher)
	if err != nil {
		return err
	}
	resp, err := c.SendRequest("POST", fmt.Sprintf("/api/teachers/%d/update?format=json", teacher.ID), data)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(resp, &response); err != nil {
		return err
	}
	if string(response.Error) != "" {
		return errors.New(string(response.Error))
	}
	return nil
}

func (c *Client) UpdateJob(job *orm.Job) error {
	var response renderer.JsonResponse

	data, err := json.Marshal(job)
	if err != nil {
		return err
	}
	resp, err := c.SendRequest("POST", fmt.Sprintf("/api/jobs/%d/update?format=json", job.ID), data)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(resp, &response); err != nil {
		return err
	}
	if string(response.Error) != "" {
		return errors.New(string(response.Error))
	}
	return nil
}

func (c *Client) UpdateUser(user orm.User) error {
	var response renderer.JsonResponse

	data, err := json.Marshal(user)
	if err != nil {
		return err
	}
	resp, err := c.SendRequest("POST", fmt.Sprintf("/api/%s/%d/update?format=json", user.RestAPIPath(), user.GetID()), data)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(resp, &response); err != nil {
		return err
	}
	if string(response.Error) != "" {
		return errors.New(string(response.Error))
	}
	return nil
}

func (c *Client) AddTeacher(teacher *orm.Teacher) error {
	var response renderer.JsonResponse

	data, err := json.Marshal(teacher)
	if err != nil {
		return err
	}
	resp, err := c.SendRequest("POST", "/api/teachers/add/?format=json", data)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(resp, &response); err != nil {
		return err
	}
	if string(response.Error) != "" {
		return errors.New(string(response.Error))
	}
	return nil
}
