package models

import (
	"net/http"
	"strings"
	"time"

	"forge.grandlyon.com/apoyen/elections/internal/auth"
	"github.com/jinzhu/gorm"

	// Needed for sqlite
	_ "github.com/jinzhu/gorm/dialects/sqlite"
)

// DataHandler init a gorm DB an presents API handlers
type DataHandler struct {
	db *gorm.DB
}

// ErrorIDDoesNotExist = "id does not exist" with 404 http.StatusNotFound
const ErrorIDDoesNotExist = "id does not exist"

// ErrorIDIsMissing = "id is missing" with 404 http.StatusNotFound
const ErrorIDIsMissing = "id is missing"

// ErrorCannotAccessRessource = "You can not access this ressource" with 403 http.StatusForbidden
const ErrorCannotAccessRessource = "You can not access this ressource"

// ErrorRoleOfLoggedUser = "Could not get role of logged user" with 500 http.StatusInternalServerError
const ErrorRoleOfLoggedUser = "Could not get role of logged user"

// ErrorNotAuthorizeMethodOnRessource = "You're not authorize to execute this method on this ressource." with 405 http.StatusMethodNotAllowed
const ErrorNotAuthorizeMethodOnRessource = "You're not authorize to execute this method on this ressource."

// ErrorParentNotFound = "Could not get the parent associated to the object" with 500 http.StatusInternalServerError
const ErrorParentNotFound = "Could not get the parent associated to the object"

// Election represent an election divided in areas with 1 or several rounds
type Election struct {
	ID         uint       `gorm:"primary_key"`
	CreatedAt  time.Time  `json:"-"`
	UpdatedAt  time.Time  `json:"-"`
	DeletedAt  *time.Time `json:"-"`
	Name       string
	BallotType string
	Areas      []Area
	Rounds     []Round
}

// Area represent an area of an election divided in one or several Sections
type Area struct {
	ID         uint       `gorm:"primary_key"`
	CreatedAt  time.Time  `json:"-"`
	UpdatedAt  time.Time  `json:"-"`
	DeletedAt  *time.Time `json:"-"`
	ElectionID uint
	Name       string
	SeatNumber uint
	MapID      string
	Sections   []Section
}

// Section represent a section of an area divided in 1 or several Desks
type Section struct {
	ID        uint       `gorm:"primary_key"`
	CreatedAt time.Time  `json:"-"`
	UpdatedAt time.Time  `json:"-"`
	DeletedAt *time.Time `json:"-"`
	AreaID    uint
	Name      string
	MapID     string
	Desks     []Desk
}

// Desk represent a Desk office to vote from a section with the number of subscribed. It can be set to be a witness desk
type Desk struct {
	ID          uint       `gorm:"primary_key"`
	CreatedAt   time.Time  `json:"-"`
	UpdatedAt   time.Time  `json:"-"`
	DeletedAt   *time.Time `json:"-"`
	SectionID   uint
	Name        string
	WitnessDesk bool
	Subscribed  uint
	DeskRounds  []DeskRound
}

// Party represent a political party or tendance
type Party struct {
	ID             uint       `gorm:"primary_key"`
	CreatedAt      time.Time  `json:"-"`
	UpdatedAt      time.Time  `json:"-"`
	DeletedAt      *time.Time `json:"-"`
	Name           string
	Color          string
	CandidateLists []CandidateList
}

// Capturer is a user who can capture the results on the desks he is affected
type Capturer struct {
	ID         uint       `gorm:"primary_key"`
	CreatedAt  time.Time  `json:"-"`
	UpdatedAt  time.Time  `json:"-"`
	DeletedAt  *time.Time `json:"-"`
	UserID     int        `gorm:"not null;unique"`
	Name       string
	DeskRounds []DeskRound `gorm:"many2many:capturer_deskrounds;"`
}

// Parameter save the parameter for a round
type Parameter struct {
	ID                uint       `gorm:"primary_key"`
	CreatedAt         time.Time  `json:"-"`
	UpdatedAt         time.Time  `json:"-"`
	DeletedAt         *time.Time `json:"-"`
	CountBlankAndNull bool
	ShowOnlyCompleted bool
	ShowMap           bool
}

// Round represent a round for an election
type Round struct {
	ID             uint       `gorm:"primary_key"`
	CreatedAt      time.Time  `json:"-"`
	UpdatedAt      time.Time  `json:"-"`
	DeletedAt      *time.Time `json:"-"`
	ElectionID     uint
	Parameter      Parameter
	Date           string
	Round          uint
	DeskRounds     []DeskRound
	CandidateLists []CandidateList
}

// DeskRound is a duplicate instance of a Desk to save the result for a round
type DeskRound struct {
	ID             uint       `gorm:"primary_key"`
	CreatedAt      time.Time  `json:"-"`
	UpdatedAt      time.Time  `json:"-"`
	DeletedAt      *time.Time `json:"-"`
	RoundID        uint
	DeskID         uint
	Capturers      []Capturer `gorm:"many2many:capturer_deskrounds;"`
	Completed      bool
	DateCompletion time.Time
	Validated      bool
	Votes          []Vote
}

// CandidateList is a list presented in an Area on an election
type CandidateList struct {
	ID         uint       `gorm:"primary_key"`
	CreatedAt  time.Time  `json:"-"`
	UpdatedAt  time.Time  `json:"-"`
	DeletedAt  *time.Time `json:"-"`
	PartyID    uint
	RoundID    uint
	Area       Area `gorm:"foreignkey:AreaRefer"`
	Name       string
	Candidates []Candidate
	Votes      []Vote
}

// Candidate is a candiate presented on a list
type Candidate struct {
	ID                       uint       `gorm:"primary_key"`
	CreatedAt                time.Time  `json:"-"`
	UpdatedAt                time.Time  `json:"-"`
	DeletedAt                *time.Time `json:"-"`
	CandidateListID          uint
	FullName                 string
	Rank                     uint
	CommunityCounseller      bool
	Birthdate                time.Time
	PotentialIncompatibility bool
	Refused                  bool
	Removed                  bool
}

// Vote represent the number of voice between a CanidateList and a Desk (+blank and null)
type Vote struct {
	ID              uint       `gorm:"primary_key"`
	CreatedAt       time.Time  `json:"-"`
	UpdatedAt       time.Time  `json:"-"`
	DeletedAt       *time.Time `json:"-"`
	DeskRoundID     uint
	CandidateListID uint
	VoiceNumber     uint
	Blank           bool
	Null            bool
}

// NewDataHandler init a DataHandler and returns a pointer to it
func NewDataHandler() *DataHandler {
	db, err := gorm.Open("sqlite3", "./data/test.db")
	if err != nil {
		panic("failed to connect database")
	}
	db.LogMode(true)

	// Migrate the schema
	db.AutoMigrate(&Capturer{})
	db.AutoMigrate(&Election{})
	db.AutoMigrate(&Area{})
	db.AutoMigrate(&Section{})
	db.AutoMigrate(&Desk{})
	db.AutoMigrate(&Round{})
	db.AutoMigrate(&DeskRound{})
	db.AutoMigrate(&Party{})
	db.AutoMigrate(&CandidateList{})
	db.AutoMigrate(&Candidate{})
	db.AutoMigrate(&Vote{})
	db.AutoMigrate(&Parameter{})
	return &DataHandler{db: db}
}

// ProcessAPI redirect API call to DataHandler to each correct API func
func (d *DataHandler) ProcessAPI(w http.ResponseWriter, r *http.Request) {
	api := strings.Split(strings.TrimPrefix(r.URL.Path, "/api/"), "/")[0]
	switch api {
	case "Capturer":
		d.handleCapturer(w, r)
	case "Election":
		d.handleElection(w, r)
	case "Area":
		d.handleArea(w, r)
	case "Section":
		d.handleSection(w, r)
	case "Desk":
		d.handleDesk(w, r)
	case "Round":
		d.handleRound(w, r)
	case "DeskRound":
		d.handleDeskRound(w, r)
	case "CapturerDeskRound":
		d.handleCapturerDeskRound(w, r)
	case "Party":
		d.handleParty(w, r)
	}

}

func (d *DataHandler) getLoggedUser(w http.ResponseWriter, r *http.Request) interface{} {
	user := auth.GetLoggedUserTechnical(w, r)
	if user.Role != "" && (user.Role == "CAPTURER") {
		var o Capturer
		if err := d.db.Where("user_id = ?", user.ID).First(&o).Error; err != nil {
			o := Capturer{UserID: user.ID, Name: user.Login}
			d.db.Create(&o)
			d.db.First(&o, user.ID)
			return o
		}
		return o
	}

	return nil
}