package desec

import (
	"context"
	"fmt"
	"net/http"
	"time"
)

// Account an account representation.
type Account struct {
	Email        string     `json:"email"`
	Password     string     `json:"password"`
	LimitDomains int        `json:"limit_domains,omitempty"`
	Created      *time.Time `json:"created,omitempty"`
}

// Captcha a captcha representation.
type Captcha struct {
	ID        string `json:"id,omitempty"`
	Challenge string `json:"challenge,omitempty"`
	Solution  string `json:"solution,omitempty"`
}

// Registration a registration representation.
type Registration struct {
	Email    string   `json:"email,omitempty"`
	Password string   `json:"password,omitempty"`
	NewEmail string   `json:"new_email,omitempty"`
	Captcha  *Captcha `json:"captcha,omitempty"`
}

// AccountService handles communication with the account related methods of the deSEC API.
//
// https://desec.readthedocs.io/en/latest/auth/account.html
type AccountService struct {
	client *Client
}

// Login Log in.
// https://desec.readthedocs.io/en/latest/auth/account.html#log-in
func (s *AccountService) Login(ctx context.Context, email, password string) (*Token, error) {
	endpoint, err := s.client.createEndpoint("auth", "login")
	if err != nil {
		return nil, fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodPost, endpoint, Account{Email: email, Password: password})
	if err != nil {
		return nil, err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		return nil, handleError(resp)
	}

	var token Token

	err = handleResponse(resp, &token)
	if err != nil {
		return nil, err
	}

	s.client.token = token.Value

	return &token, nil
}

// Logout log out (= delete current token).
// https://desec.readthedocs.io/en/latest/auth/account.html#log-out
func (s *AccountService) Logout(ctx context.Context) error {
	endpoint, err := s.client.createEndpoint("auth", "logout")
	if err != nil {
		return fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodPost, endpoint, nil)
	if err != nil {
		return err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusNoContent {
		return handleError(resp)
	}

	s.client.token = ""

	return nil
}

// ObtainCaptcha Obtain a captcha.
// https://desec.readthedocs.io/en/latest/auth/account.html#obtain-a-captcha
func (s *AccountService) ObtainCaptcha(ctx context.Context) (*Captcha, error) {
	endpoint, err := s.client.createEndpoint("captcha")
	if err != nil {
		return nil, fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodPost, endpoint, nil)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		return nil, handleError(resp)
	}

	var captcha Captcha

	err = handleResponse(resp, &captcha)
	if err != nil {
		return nil, err
	}

	return &captcha, nil
}

// Register register account.
// https://desec.readthedocs.io/en/latest/auth/account.html#register-account
func (s *AccountService) Register(ctx context.Context, registration Registration) error {
	endpoint, err := s.client.createEndpoint("auth")
	if err != nil {
		return fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodPost, endpoint, registration)
	if err != nil {
		return err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusAccepted {
		return handleError(resp)
	}

	return nil
}

// RetrieveInformation retrieve account information.
// https://desec.readthedocs.io/en/latest/auth/account.html#retrieve-account-information
func (s *AccountService) RetrieveInformation(ctx context.Context) (*Account, error) {
	endpoint, err := s.client.createEndpoint("auth", "account")
	if err != nil {
		return nil, fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodPost, endpoint, nil)
	if err != nil {
		return nil, err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		return nil, handleError(resp)
	}

	var account Account

	err = handleResponse(resp, &account)
	if err != nil {
		return nil, err
	}

	return &account, nil
}

// PasswordReset password reset and password change.
// https://desec.readthedocs.io/en/latest/auth/account.html#password-reset
// https://desec.readthedocs.io/en/latest/auth/account.html#password-change
func (s *AccountService) PasswordReset(ctx context.Context, email string, captcha Captcha) error {
	endpoint, err := s.client.createEndpoint("auth", "account", "reset-password")
	if err != nil {
		return fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodPost, endpoint, Registration{Email: email, Captcha: &captcha})
	if err != nil {
		return err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusAccepted {
		return handleError(resp)
	}

	return nil
}

// ChangeEmail changes email address.
// https://desec.readthedocs.io/en/latest/auth/account.html#change-email-address
func (s *AccountService) ChangeEmail(ctx context.Context, email, password, newEmail string) error {
	endpoint, err := s.client.createEndpoint("auth", "account", "change-email")
	if err != nil {
		return fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodPost, endpoint, Registration{Email: email, Password: password, NewEmail: newEmail})
	if err != nil {
		return err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusAccepted {
		return handleError(resp)
	}

	return nil
}

// Delete deletes account.
// https://desec.readthedocs.io/en/latest/auth/account.html#delete-account
func (s *AccountService) Delete(ctx context.Context, email, password string) error {
	endpoint, err := s.client.createEndpoint("auth", "account", "delete")
	if err != nil {
		return fmt.Errorf("failed to create endpoint: %w", err)
	}

	req, err := s.client.newRequest(ctx, http.MethodPost, endpoint, Account{Email: email, Password: password})
	if err != nil {
		return err
	}

	resp, err := s.client.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("failed to call API: %w", err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusAccepted {
		return handleError(resp)
	}

	return nil
}
