package main

import (
	"fmt"
	"net/http"
	"strconv"
	"time"

	"zeitkort-web/repository"
)

// mainView is the data backing the working area (#main).
type mainView struct {
	Version              string
	ResetAt              time.Time
	ResetIntervalMinutes int
	Companies            []repository.Company
	CompanyID            int64
	Assignments          []repository.Assignment
	AssignmentID         int64
	Assignment           *repository.Assignment
	Tasks                []repository.Task
	Sessions             []repository.WorkSession
	TotalHours           float64
	TotalAmount          float64
	Running              *repository.WorkSession
	EditingTaskID        int64
	AddingTask           bool
	Notice               string
	AppError             string
}

func qInt64(r *http.Request, key string) int64 {
	n, _ := strconv.ParseInt(r.URL.Query().Get(key), 10, 64)
	return n
}

func formInt64(r *http.Request, key string) int64 {
	n, _ := strconv.ParseInt(r.FormValue(key), 10, 64)
	return n
}

func pathInt64(r *http.Request, key string) int64 {
	n, _ := strconv.ParseInt(r.PathValue(key), 10, 64)
	return n
}

// buildMainView assembles everything shown for the current selection.
func (s *Server) buildMainView(companyID, assignmentID int64) mainView {
	v := mainView{
		Version:              Version,
		CompanyID:            companyID,
		AssignmentID:         assignmentID,
		ResetAt:              s.nextResetAt(),
		ResetIntervalMinutes: int(resetInterval / time.Minute),
	}

	companies, err := s.repo.AllCompanies()
	if err != nil {
		v.AppError = err.Error()
		return v
	}
	v.Companies = companies

	if companyID == 0 {
		return v
	}

	assignments, err := s.repo.AssignmentsByCompany(companyID)
	if err != nil {
		v.AppError = err.Error()
		return v
	}
	v.Assignments = assignments

	if assignmentID == 0 {
		return v
	}
	for i := range assignments {
		if assignments[i].ID == assignmentID {
			v.Assignment = &assignments[i]
			break
		}
	}
	if v.Assignment == nil {
		// Assignment does not belong to this company; treat as unselected.
		v.AssignmentID = 0
		return v
	}

	if tasks, err := s.repo.TasksByAssignment(assignmentID); err == nil {
		v.Tasks = tasks
	}
	if sessions, err := s.repo.WorkSessionsByAssignment(assignmentID, 20); err == nil {
		v.Sessions = sessions
		for i := range sessions {
			if sessions[i].EndTime == nil {
				v.Running = &sessions[i]
				break
			}
		}
	}
	v.TotalHours, _ = s.repo.TotalHoursForAssignment(assignmentID)
	v.TotalAmount = v.TotalHours * v.Assignment.HourlyRate
	return v
}

// ── Main working area ───────────────────────────────────────────────────────

func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
	v := s.buildMainView(qInt64(r, "company_id"), qInt64(r, "assignment_id"))
	s.render(w, "page", v)
}

func (s *Server) handleMain(w http.ResponseWriter, r *http.Request) {
	v := s.buildMainView(qInt64(r, "company_id"), qInt64(r, "assignment_id"))
	s.render(w, "main", v)
}

// renderMainWith re-renders the working area fragment for the given selection,
// optionally carrying a notice or error to show the user.
func (s *Server) renderMainWith(w http.ResponseWriter, companyID, assignmentID int64, notice, appErr string) {
	v := s.buildMainView(companyID, assignmentID)
	if notice != "" {
		v.Notice = notice
	}
	if appErr != "" {
		v.AppError = appErr
	}
	s.render(w, "main", v)
}

// ── Companies ───────────────────────────────────────────────────────────────

type companyFormView struct {
	Company   *repository.Company
	CompanyID int64
}

func (s *Server) handleCompanyForm(w http.ResponseWriter, r *http.Request) {
	view := companyFormView{}
	if id := pathInt64(r, "id"); id > 0 {
		c, err := s.repo.GetCompanyByID(id)
		if err != nil {
			s.renderMainWith(w, 0, 0, "", err.Error())
			return
		}
		view.Company = c
		view.CompanyID = id
	}
	s.render(w, "company_form", view)
}

func (s *Server) handleCompanySave(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	rate, _ := strconv.ParseFloat(r.FormValue("hourly_rate"), 64)
	c := repository.Company{
		Name:         r.FormValue("name"),
		Address:      r.FormValue("address"),
		ContactEmail: r.FormValue("contact_email"),
		ContactPhone: r.FormValue("contact_phone"),
		HourlyRate:   rate,
	}

	if id := pathInt64(r, "id"); id > 0 {
		if err := s.repo.UpdateCompany(id, c); err != nil {
			s.renderMainWith(w, 0, 0, "", err.Error())
			return
		}
		s.renderMainWith(w, id, 0, "Company updated.", "")
		return
	}

	created, err := s.repo.InsertCompany(c)
	if err != nil {
		s.renderMainWith(w, 0, 0, "", err.Error())
		return
	}
	s.renderMainWith(w, created.ID, 0, "Company created.", "")
}

func (s *Server) handleCompanyDelete(w http.ResponseWriter, r *http.Request) {
	id := pathInt64(r, "id")
	if err := s.repo.DeleteCompany(id); err != nil {
		s.renderMainWith(w, id, 0, "", "Cannot delete: remove its assignments first.")
		return
	}
	s.renderMainWith(w, 0, 0, "Company deleted.", "")
}

// ── Assignments ─────────────────────────────────────────────────────────────

type assignmentFormView struct {
	Assignment *repository.Assignment
	CompanyID  int64
	AssignID   int64
}

func (s *Server) handleAssignmentForm(w http.ResponseWriter, r *http.Request) {
	view := assignmentFormView{CompanyID: qInt64(r, "company_id")}
	if id := pathInt64(r, "id"); id > 0 {
		a, err := s.repo.GetAssignmentByID(id)
		if err != nil {
			s.renderMainWith(w, view.CompanyID, 0, "", err.Error())
			return
		}
		view.Assignment = a
		view.AssignID = id
		view.CompanyID = a.CompanyID
	}
	s.render(w, "assignment_form", view)
}

func (s *Server) handleAssignmentSave(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	companyID := formInt64(r, "company_id")
	rate, _ := strconv.ParseFloat(r.FormValue("hourly_rate"), 64)

	// fall back to the company's default rate if none given
	if rate == 0 {
		if c, err := s.repo.GetCompanyByID(companyID); err == nil {
			rate = c.HourlyRate
		}
	}

	if id := pathInt64(r, "id"); id > 0 {
		status := r.FormValue("status")
		if status == "" {
			status = "active"
		}
		a := repository.Assignment{
			Name:        r.FormValue("name"),
			Description: r.FormValue("description"),
			HourlyRate:  rate,
			Status:      status,
		}
		if err := s.repo.UpdateAssignment(id, a); err != nil {
			s.renderMainWith(w, companyID, 0, "", err.Error())
			return
		}
		s.renderMainWith(w, companyID, id, "Assignment updated.", "")
		return
	}

	a := repository.Assignment{
		CompanyID:   companyID,
		Name:        r.FormValue("name"),
		Description: r.FormValue("description"),
		HourlyRate:  rate,
	}
	created, err := s.repo.InsertAssignment(a)
	if err != nil {
		s.renderMainWith(w, companyID, 0, "", err.Error())
		return
	}
	s.renderMainWith(w, companyID, created.ID, "Assignment created.", "")
}

func (s *Server) handleAssignmentDelete(w http.ResponseWriter, r *http.Request) {
	id := pathInt64(r, "id")
	companyID := qInt64(r, "company_id")
	if err := s.repo.DeleteAssignment(id); err != nil {
		s.renderMainWith(w, companyID, id, "", "Cannot delete: remove its tasks first.")
		return
	}
	s.renderMainWith(w, companyID, 0, "Assignment deleted.", "")
}

// ── Tasks ───────────────────────────────────────────────────────────────────

func (s *Server) handleTaskNewForm(w http.ResponseWriter, r *http.Request) {
	companyID := qInt64(r, "company_id")
	assignmentID := qInt64(r, "assignment_id")
	v := s.buildMainView(companyID, assignmentID)
	v.AddingTask = true
	s.render(w, "main", v)
}

func (s *Server) handleTaskEditForm(w http.ResponseWriter, r *http.Request) {
	companyID := qInt64(r, "company_id")
	assignmentID := qInt64(r, "assignment_id")
	v := s.buildMainView(companyID, assignmentID)
	v.EditingTaskID = pathInt64(r, "id")
	s.render(w, "main", v)
}

func (s *Server) handleTaskCreate(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	companyID := formInt64(r, "company_id")
	assignmentID := formInt64(r, "assignment_id")
	name := r.FormValue("name")
	if name == "" {
		s.renderMainWith(w, companyID, assignmentID, "", "Enter a task name.")
		return
	}
	if _, err := s.repo.InsertTask(repository.Task{AssignmentID: assignmentID, Name: name}); err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", err.Error())
		return
	}
	s.renderMainWith(w, companyID, assignmentID, "", "")
}

func (s *Server) handleTaskUpdate(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	id := pathInt64(r, "id")
	companyID := formInt64(r, "company_id")
	assignmentID := formInt64(r, "assignment_id")
	name := r.FormValue("name")
	if name == "" {
		s.renderMainWith(w, companyID, assignmentID, "", "Task name cannot be empty.")
		return
	}
	if err := s.repo.UpdateTask(id, name); err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", err.Error())
		return
	}
	s.renderMainWith(w, companyID, assignmentID, "Task renamed.", "")
}

func (s *Server) handleTaskDelete(w http.ResponseWriter, r *http.Request) {
	id := pathInt64(r, "id")
	companyID := qInt64(r, "company_id")
	assignmentID := qInt64(r, "assignment_id")
	if err := s.repo.DeleteTask(id); err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", "Cannot delete: remove its work sessions first.")
		return
	}
	s.renderMainWith(w, companyID, assignmentID, "Task deleted.", "")
}

// ── Work sessions: timer ──────────────────────────────────────────────────────

func (s *Server) handleSessionStart(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	companyID := formInt64(r, "company_id")
	assignmentID := formInt64(r, "assignment_id")
	taskID := formInt64(r, "task_id")

	if assignmentID == 0 {
		s.renderMainWith(w, companyID, assignmentID, "", "Pick an assignment first.")
		return
	}
	if taskID == 0 {
		s.renderMainWith(w, companyID, assignmentID, "", "Select a task to start the timer.")
		return
	}
	task, err := s.repo.GetTaskByID(taskID)
	if err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", err.Error())
		return
	}
	desc := fmt.Sprintf("%d - %s", task.Number, task.Name)
	if _, err := s.repo.InsertWorkSession(repository.WorkSession{
		AssignmentID:    assignmentID,
		TaskID:          taskID,
		TaskDescription: desc,
	}); err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", err.Error())
		return
	}
	s.renderMainWith(w, companyID, assignmentID, "", "")
}

func (s *Server) handleSessionStop(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	id := pathInt64(r, "id")
	companyID := formInt64(r, "company_id")
	assignmentID := formInt64(r, "assignment_id")
	if err := s.repo.StopWorkSession(id); err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", err.Error())
		return
	}
	s.renderMainWith(w, companyID, assignmentID, "", "")
}

// ── Work sessions: manual entry + edit ────────────────────────────────────────

type sessionFormView struct {
	Session      *repository.WorkSession
	SessionID    int64
	CompanyID    int64
	AssignmentID int64
	Tasks        []repository.Task
}

func (s *Server) handleSessionForm(w http.ResponseWriter, r *http.Request) {
	view := sessionFormView{
		CompanyID:    qInt64(r, "company_id"),
		AssignmentID: qInt64(r, "assignment_id"),
	}
	if id := pathInt64(r, "id"); id > 0 {
		// editing: load the session via its assignment's session list
		view.SessionID = id
		// We already know the assignment from the query string.
		sessions, _ := s.repo.WorkSessionsByAssignment(view.AssignmentID, 1000)
		for i := range sessions {
			if sessions[i].ID == id {
				view.Session = &sessions[i]
				break
			}
		}
	}
	if view.AssignmentID > 0 {
		view.Tasks, _ = s.repo.TasksByAssignment(view.AssignmentID)
	}
	s.render(w, "session_form", view)
}

func (s *Server) handleSessionSave(w http.ResponseWriter, r *http.Request) {
	r.ParseForm()
	companyID := formInt64(r, "company_id")
	assignmentID := formInt64(r, "assignment_id")

	// Resolve the task (existing id, or a new task by name).
	var task *repository.Task
	if r.FormValue("task_id") == "new" {
		name := r.FormValue("new_task_name")
		if name == "" {
			s.renderMainWith(w, companyID, assignmentID, "", "Enter a name for the new task.")
			return
		}
		created, err := s.repo.InsertTask(repository.Task{AssignmentID: assignmentID, Name: name})
		if err != nil {
			s.renderMainWith(w, companyID, assignmentID, "", err.Error())
			return
		}
		task = created
	} else {
		t, err := s.repo.GetTaskByID(formInt64(r, "task_id"))
		if err != nil {
			s.renderMainWith(w, companyID, assignmentID, "", "Select a task.")
			return
		}
		task = t
	}

	loc := time.Now().Location()
	dateParsed, err := time.ParseInLocation("02-01-2006", r.FormValue("date"), loc)
	if err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", "Invalid date (use DD-MM-YYYY).")
		return
	}
	startParsed, err := time.Parse("15:04", r.FormValue("start"))
	if err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", "Invalid start time (use HH:MM).")
		return
	}
	endParsed, err := time.Parse("15:04", r.FormValue("end"))
	if err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", "Invalid end time (use HH:MM).")
		return
	}
	startTime := time.Date(dateParsed.Year(), dateParsed.Month(), dateParsed.Day(),
		startParsed.Hour(), startParsed.Minute(), 0, 0, loc)
	endTime := time.Date(dateParsed.Year(), dateParsed.Month(), dateParsed.Day(),
		endParsed.Hour(), endParsed.Minute(), 0, 0, loc)
	if endTime.Before(startTime) {
		endTime = endTime.Add(24 * time.Hour)
	}

	desc := fmt.Sprintf("%d - %s", task.Number, task.Name)

	if id := pathInt64(r, "id"); id > 0 {
		if err := s.repo.UpdateWorkSession(id, repository.WorkSession{
			TaskID: task.ID, TaskDescription: desc, StartTime: startTime, EndTime: &endTime,
		}); err != nil {
			s.renderMainWith(w, companyID, assignmentID, "", err.Error())
			return
		}
		s.renderMainWith(w, companyID, assignmentID, "Session updated.", "")
		return
	}

	if _, err := s.repo.InsertManualWorkSession(repository.WorkSession{
		AssignmentID: assignmentID, TaskID: task.ID,
		TaskDescription: desc, StartTime: startTime, EndTime: &endTime,
	}); err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", err.Error())
		return
	}
	s.renderMainWith(w, companyID, assignmentID, "Session added.", "")
}

func (s *Server) handleSessionDelete(w http.ResponseWriter, r *http.Request) {
	id := pathInt64(r, "id")
	companyID := qInt64(r, "company_id")
	assignmentID := qInt64(r, "assignment_id")
	if err := s.repo.DeleteWorkSession(id); err != nil {
		s.renderMainWith(w, companyID, assignmentID, "", err.Error())
		return
	}
	s.renderMainWith(w, companyID, assignmentID, "Session deleted.", "")
}
