package api

import (
	"errors"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"

	"fiko-ai/internal/pdks"
	"fiko-ai/internal/protocol"
)

// PDKS uclari: bolum, personel, vardiya, giris/cikis, mola, izin ve raporlar.
//
// YETKI: bu uclar YAZMA yapar (devam kaydi tutar), bu yuzden yalniz yonetici
// rolune aciktir. Calisan kendi devamini gorur ama KAYIT TUTMAZ; aksi halde
// "ben 8 saat calistim" kaydi kendi sayilir ve veri butunlugu kaybolur.
// Yetki denetimi route seviyesinde yapilir (registerRoutes, bkz. api.go).
//
// strings.Clone NEDEN? Fiber'in c.Params() degeri havuzdan gelir ve handler
// bittikten sonra bozulur (bkz. ILERLEME.md notu). Yonlendirilen degiskene
// yazarken klonlanir.

// pdksAvailable PDKS yapilandirilmamis ise 503 doner.
func (s *Server) pdksAvailable(c *fiber.Ctx) error {
	if s.deps.Pdks == nil {
		return fail(c, fiber.StatusServiceUnavailable, protocol.ErrCodeInternal,
			"PDKS yapilandirilmamis")
	}
	return c.Next()
}

// failPdks, PDKS hatalarini HTTP durumuna cevirir.
//
// AYRIM: ErrNotFound -> 404, ErrInvalid -> 400, ErrConflict -> 409.
// ErrConflict ayri tutulur: "zaten giris yapmissin" bir hata degil, kullaniciya
// ne yapmasi gerektigini soyleyen bir durumdur; 400 genel gecersizlik gibi
// gorunur ve kullanici ne yapacagini bilemez.
func failPdks(c *fiber.Ctx, err error) error {
	switch {
	case errors.Is(err, pdks.ErrNotFound):
		return fail(c, fiber.StatusNotFound, protocol.ErrCodeNotFound, "bulunamadi")
	case errors.Is(err, pdks.ErrInvalid):
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeValidation, err.Error())
	case errors.Is(err, pdks.ErrConflict):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, err.Error())
	case errors.Is(err, pdks.ErrDuplicate):
		return fail(c, fiber.StatusConflict, protocol.ErrCodeConflict, err.Error())
	default:
		return fail(c, fiber.StatusInternalServerError, protocol.ErrCodeInternal,
			"PDKS islemi basarisiz")
	}
}

// ---- bolum ----

// listDepartments GET /api/pdks/departments
func (s *Server) listDepartments(c *fiber.Ctx) error {
	items, err := s.deps.Pdks.ListDepartments(c.UserContext())
	if err != nil {
		return failPdks(c, err)
	}
	if items == nil {
		items = []pdks.Department{}
	}
	return c.JSON(fiber.Map{"items": items, "count": len(items)})
}

type departmentRequest struct {
	Name        string  `json:"name"`
	ParentID    string  `json:"parentId"`
	Code        string  `json:"code"`
	ManagerID   string  `json:"managerId"`
	WeeklyHours float64 `json:"weeklyHours"`
	Active      *bool   `json:"active"`
}

// createDepartment POST /api/pdks/departments
func (s *Server) createDepartment(c *fiber.Ctx) error {
	var in departmentRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "gecersiz istek govdesi")
	}
	active := true
	if in.Active != nil {
		active = *in.Active
	}
	d, err := s.deps.Pdks.CreateDepartment(c.UserContext(), pdks.Department{
		Name: in.Name, ParentID: in.ParentID, Code: in.Code,
		ManagerID: in.ManagerID, WeeklyHours: in.WeeklyHours, Active: active,
	})
	if err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.department.create", d.ID)
	return c.Status(fiber.StatusCreated).JSON(d)
}

// updateDepartment PUT /api/pdks/departments/:id
func (s *Server) updateDepartment(c *fiber.Ctx) error {
	var in departmentRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "gecersiz istek govdesi")
	}
	id := strings.Clone(c.Params("id"))
	cur, err := s.deps.Pdks.GetDepartment(c.UserContext(), id)
	if err != nil {
		return failPdks(c, err)
	}
	cur.Name = in.Name
	cur.ParentID = in.ParentID
	cur.Code = in.Code
	cur.ManagerID = in.ManagerID
	cur.WeeklyHours = in.WeeklyHours
	if in.Active != nil {
		cur.Active = *in.Active
	}
	d, err := s.deps.Pdks.UpdateDepartment(c.UserContext(), cur)
	if err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.department.update", d.ID)
	return c.JSON(d)
}

// deleteDepartment DELETE /api/pdks/departments/:id
func (s *Server) deleteDepartment(c *fiber.Ctx) error {
	id := strings.Clone(c.Params("id"))
	if err := s.deps.Pdks.DeleteDepartment(c.UserContext(), id); err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.department.delete", id)
	return c.SendStatus(fiber.StatusNoContent)
}

// ---- personel ----

// listEmployees GET /api/pdks/employees
func (s *Server) listEmployees(c *fiber.Ctx) error {
	items, err := s.deps.Pdks.ListEmployees(c.UserContext())
	if err != nil {
		return failPdks(c, err)
	}
	if items == nil {
		items = []pdks.Employee{}
	}
	return c.JSON(fiber.Map{"items": items, "count": len(items)})
}

type employeeRequest struct {
	Name         string  `json:"name"`
	UserID       string  `json:"userId"`
	DepartmentID string  `json:"departmentId"`
	Title        string  `json:"title"`
	Email        string  `json:"email"`
	Phone        string  `json:"phone"`
	HireDate     string  `json:"hireDate"`
	WeeklyHours  float64 `json:"weeklyHours"`
	AnnualLeave  float64 `json:"annualLeave"`
	AutoTrack    *bool   `json:"autoTrack"`
	Active       *bool   `json:"active"`
}

// createEmployee POST /api/pdks/employees
func (s *Server) createEmployee(c *fiber.Ctx) error {
	var in employeeRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "gecersiz istek govdesi")
	}
	active := true
	if in.Active != nil {
		active = *in.Active
	}
	e, err := s.deps.Pdks.CreateEmployee(c.UserContext(), pdks.Employee{
		Name: in.Name, UserID: in.UserID, DepartmentID: in.DepartmentID, Title: in.Title,
		Email: in.Email, Phone: in.Phone, HireDate: in.HireDate, WeeklyHours: in.WeeklyHours,
		AnnualLeave: in.AnnualLeave, AutoTrack: in.AutoTrack != nil && *in.AutoTrack, Active: active,
	})
	if err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.employee.create", e.ID)
	return c.Status(fiber.StatusCreated).JSON(e)
}

// updateEmployee PUT /api/pdks/employees/:id
func (s *Server) updateEmployee(c *fiber.Ctx) error {
	var in employeeRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "gecersiz istek govdesi")
	}
	id := strings.Clone(c.Params("id"))
	cur, err := s.deps.Pdks.GetEmployee(c.UserContext(), id)
	if err != nil {
		return failPdks(c, err)
	}
	cur.Name = in.Name
	cur.UserID = in.UserID
	cur.DepartmentID = in.DepartmentID
	cur.Title = in.Title
	cur.Email = in.Email
	cur.Phone = in.Phone
	cur.HireDate = in.HireDate
	cur.WeeklyHours = in.WeeklyHours
	cur.AnnualLeave = in.AnnualLeave
	if in.AutoTrack != nil {
		cur.AutoTrack = *in.AutoTrack
	}
	if in.Active != nil {
		cur.Active = *in.Active
	}
	e, err := s.deps.Pdks.UpdateEmployee(c.UserContext(), cur)
	if err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.employee.update", e.ID)
	return c.JSON(e)
}

// deleteEmployee DELETE /api/pdks/employees/:id
func (s *Server) deleteEmployee(c *fiber.Ctx) error {
	id := strings.Clone(c.Params("id"))
	if err := s.deps.Pdks.DeleteEmployee(c.UserContext(), id); err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.employee.delete", id)
	return c.SendStatus(fiber.StatusNoContent)
}

// ---- vardiya ----

// listShifts GET /api/pdks/shifts
func (s *Server) listShifts(c *fiber.Ctx) error {
	items, err := s.deps.Pdks.ListShifts(c.UserContext())
	if err != nil {
		return failPdks(c, err)
	}
	if items == nil {
		items = []pdks.Shift{}
	}
	return c.JSON(fiber.Map{"items": items, "count": len(items)})
}

type shiftRequest struct {
	Name        string  `json:"name"`
	Start       string  `json:"start"`
	End         string  `json:"end"`
	BreakMin    int     `json:"breakMin"`
	WeeklyHours float64 `json:"weeklyHours"`
	Days        []int   `json:"days"`
	Active      *bool   `json:"active"`
}

// createShift POST /api/pdks/shifts
func (s *Server) createShift(c *fiber.Ctx) error {
	var in shiftRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "gecersiz istek govdesi")
	}
	active := true
	if in.Active != nil {
		active = *in.Active
	}
	sh, err := s.deps.Pdks.CreateShift(c.UserContext(), pdks.Shift{
		Name: in.Name, Start: in.Start, End: in.End, BreakMin: in.BreakMin,
		WeeklyHours: in.WeeklyHours, Days: in.Days, Active: active,
	})
	if err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.shift.create", sh.ID)
	return c.Status(fiber.StatusCreated).JSON(sh)
}

// updateShift PUT /api/pdks/shifts/:id
func (s *Server) updateShift(c *fiber.Ctx) error {
	var in shiftRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "gecersiz istek govdesi")
	}
	id := strings.Clone(c.Params("id"))
	cur, err := s.deps.Pdks.GetShift(c.UserContext(), id)
	if err != nil {
		return failPdks(c, err)
	}
	cur.Name = in.Name
	cur.Start = in.Start
	cur.End = in.End
	cur.BreakMin = in.BreakMin
	cur.WeeklyHours = in.WeeklyHours
	cur.Days = in.Days
	if in.Active != nil {
		cur.Active = *in.Active
	}
	sh, err := s.deps.Pdks.UpdateShift(c.UserContext(), cur)
	if err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.shift.update", sh.ID)
	return c.JSON(sh)
}

// deleteShift DELETE /api/pdks/shifts/:id
func (s *Server) deleteShift(c *fiber.Ctx) error {
	id := strings.Clone(c.Params("id"))
	if err := s.deps.Pdks.DeleteShift(c.UserContext(), id); err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.shift.delete", id)
	return c.SendStatus(fiber.StatusNoContent)
}

// ---- devam: giris / cikis / mola ----

type punchRequest struct {
	EmpID string `json:"empId"`
	Type  string `json:"type"` // in | out | break_start | break_end
	// At bos ise sunucu saati kullanilir. Geriye donuk kayit (düzeltme) icin
	// ISO-8601 gonderilir: "2026-10-05T09:00:00".
	At   string `json:"at"`
	Note string `json:"note"`
	// Source "import" ise toplu iceri aktarim sayilir (denetim izinde ayirt edilir).
	Source string `json:"source"`
}

// punchAttendance POST /api/pdks/attendance
//
// AYNI KULLANICI iki kez cagirirsa 409 doner ("zaten giris yapmissin"). Bu
// beklenen davranistir: model iki kez arac cagirirsa sessizce ikinci kaydi
// YAZILMAZ; hatayi alip tekrar denemek zorunda kalir.
func (s *Server) punchAttendance(c *fiber.Ctx) error {
	var in punchRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "gecersiz istek govdesi")
	}
	p := pdks.Punch{
		EmpID:  in.EmpID,
		Type:   pdks.AttendanceType(strings.TrimSpace(in.Type)),
		Note:   in.Note,
		Source: in.Source,
	}
	if strings.TrimSpace(in.At) != "" {
		at, err := parseStamp(in.At)
		if err != nil {
			return fail(c, fiber.StatusBadRequest, protocol.ErrCodeValidation,
				"at bicimi RFC3339 olmali (orn. 2026-10-05T09:00:00)")
		}
		p.At = at
	}
	a, err := s.deps.Pdks.Punch(c.UserContext(), p)
	if err != nil {
		s.auditFailure(s.currentUser(c), "pdks.attendance.punch", in.EmpID+": "+err.Error())
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.attendance.punch", a.ID)
	return c.Status(fiber.StatusCreated).JSON(a)
}

// deleteAttendance DELETE /api/pdks/attendance/:id — yanlis kaydi DUZELTME.
//
// Neden var? Personel saatleri yanlis girdiginde kaydi once baglayip sonra
// dogru kaydi eklemek tek secenektir (olay modeli sayesinde). Bu uc YONETICIYE
// aciktir ve her silme denetim kaydina yazilir.
func (s *Server) deleteAttendance(c *fiber.Ctx) error {
	id := strings.Clone(c.Params("id"))
	user := s.currentUser(c)
	if err := s.deps.Pdks.DeleteAttendance(c.UserContext(), id); err != nil {
		s.auditFailure(user, "pdks.attendance.delete", id+": "+err.Error())
		return failPdks(c, err)
	}
	s.auditSuccess(user, "pdks.attendance.delete", id)
	return c.SendStatus(fiber.StatusNoContent)
}

// listAttendance GET /api/pdks/attendance?empId=&from=&to=
func (s *Server) listAttendance(c *fiber.Ctx) error {
	items, err := s.deps.Pdks.ListAttendance(c.UserContext(), pdks.AttendFilter{
		EmpID: c.Query("empId"),
		From:  c.Query("from"),
		To:    c.Query("to"),
		Type:  pdks.AttendanceType(c.Query("type")),
	})
	if err != nil {
		return failPdks(c, err)
	}
	if items == nil {
		items = []pdks.Attendance{}
	}
	return c.JSON(fiber.Map{"items": items, "count": len(items)})
}

// ---- izin ----

type leaveRequest struct {
	EmpID    string  `json:"empId"`
	Kind     string  `json:"kind"`
	From     string  `json:"from"`
	To       string  `json:"to"`
	Days     float64 `json:"days"`
	Approved *bool   `json:"approved"`
	Reason   string  `json:"reason"`
}

// createLeave POST /api/pdks/leaves
func (s *Server) createLeave(c *fiber.Ctx) error {
	var in leaveRequest
	if err := c.BodyParser(&in); err != nil {
		return fail(c, fiber.StatusBadRequest, protocol.ErrCodeBadRequest, "gecersiz istek govdesi")
	}
	l, err := s.deps.Pdks.CreateLeave(c.UserContext(), pdks.Leave{
		EmpID: in.EmpID, Kind: in.Kind, From: in.From, To: in.To, Days: in.Days,
		Approved: in.Approved != nil && *in.Approved, Reason: in.Reason,
	})
	if err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.leave.create", l.ID)
	return c.Status(fiber.StatusCreated).JSON(l)
}

// listLeaves GET /api/pdks/leaves?empId=&from=&to=
func (s *Server) listLeaves(c *fiber.Ctx) error {
	items, err := s.deps.Pdks.ListLeave(c.UserContext(), c.Query("empId"), c.Query("from"), c.Query("to"))
	if err != nil {
		return failPdks(c, err)
	}
	if items == nil {
		items = []pdks.Leave{}
	}
	return c.JSON(fiber.Map{"items": items, "count": len(items)})
}

// deleteLeave DELETE /api/pdks/leaves/:id
func (s *Server) deleteLeave(c *fiber.Ctx) error {
	id := strings.Clone(c.Params("id"))
	if err := s.deps.Pdks.DeleteLeave(c.UserContext(), id); err != nil {
		return failPdks(c, err)
	}
	s.auditSuccess(s.currentUser(c), "pdks.leave.delete", id)
	return c.SendStatus(fiber.StatusNoContent)
}

// ---- raporlar ----

// dailyReport GET /api/pdks/reports/daily?day=YYYY-MM-DD
//
// Bir gunun TUM personelinin devam tablosudur (PDKS panelinin ana ekrani).
func (s *Server) dailyReport(c *fiber.Ctx) error {
	items, err := s.deps.Pdks.DailyRange(c.UserContext(), c.Query("day"))
	if err != nil {
		return failPdks(c, err)
	}
	if items == nil {
		items = []pdks.Daily{}
	}
	return c.JSON(fiber.Map{"items": items, "count": len(items)})
}

// rangeReport GET /api/pdks/reports/range?empId=&from=&to=
//
// Haftalik/aylik toplam raporu. Aralik siniri Service tarafinda 366 gundur.
func (s *Server) rangeReport(c *fiber.Ctx) error {
	rep, err := s.deps.Pdks.Report(c.UserContext(), c.Query("empId"), c.Query("from"), c.Query("to"))
	if err != nil {
		return failPdks(c, err)
	}
	return c.JSON(rep)
}

// dayDetail GET /api/pdks/reports/day?empId=&day=YYYY-MM-DD
//
// TEK personelin gununu, olay sirasiyla birlikte dondurur. Yanlis kayit
// duzeltmesi bu ekrandan yapildigi icin OLAY LISTESI de gereklidir (sadece
// ozet degil: kullanici hangi kaydi silmesi gerektigini gormelidir).
func (s *Server) dayDetail(c *fiber.Ctx) error {
	empID := c.Query("empId")
	day := c.Query("day")
	sum, err := s.deps.Pdks.Day(c.UserContext(), empID, day)
	if err != nil {
		return failPdks(c, err)
	}
	if day == "" {
		day = sum.DayOf
	}
	evs, err := s.deps.Pdks.ListAttendance(c.UserContext(), pdks.AttendFilter{
		EmpID: empID, From: day, To: day,
	})
	if err != nil {
		return failPdks(c, err)
	}
	return c.JSON(fiber.Map{"summary": sum, "events": evs})
}

// parseStamp, istemciden gelen zaman damgasini cozer.
//
// IKI BICIM KABUL EDILIR ve bu BILINCLI bir tercihtir:
//   - RFC3339: "2026-10-05T09:00:00+03:00" (zaman dilimi acik — en dogru bicim)
//   - "2006-01-02T15:04:05": "2026-10-05T09:00:00" (zaman dilimi YOK)
//
// Neden ikincisi de? Canli hata: Go`nin time.Parse(time.RFC3339, ...)
// fonksiyonu zaman dilimi OLMAYAN girdiyi reddeder. Model ve insan eliyle
// "2026-10-05T09:00:00" yazmak çok doğal ama 400 alıyordu; kullanıcı
// hatayı anlamadan "format yanlış" sanıyordu. Yerel saat varsayımı ile
// yorumlanır (PDKS yerel saatle çalışır — bkz. pdks.DayOf).
func parseStamp(v string) (time.Time, error) {
	s := strings.TrimSpace(v)
	if t, err := time.Parse(time.RFC3339, s); err == nil {
		return t, nil
	}
	if t, err := time.ParseInLocation("2006-01-02T15:04:05", s, time.Local); err == nil {
		return t, nil
	}
	return time.Time{}, errors.New("pdks: gecersiz zaman damgasi")
}
