package repository

import (
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"
)

// MemoryRepository implements Repository with process-local memory only.
// It is intended for web-only proof-of-concept deployments where data should
// disappear periodically and never be written to disk.
type MemoryRepository struct {
	mu sync.RWMutex

	nextCompanyID    int64
	nextAssignmentID int64
	nextTaskID       int64
	nextSessionID    int64
	nextInvoiceID    int64

	companies   map[int64]Company
	assignments map[int64]Assignment
	tasks       map[int64]Task
	sessions    map[int64]WorkSession
	invoices    map[int64]Invoice
}

func NewMemoryRepository() *MemoryRepository {
	repo := &MemoryRepository{}
	repo.Reset()
	return repo
}

// Reset wipes every in-memory record and restarts all ids at 1.
func (repo *MemoryRepository) Reset() {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	repo.nextCompanyID = 1
	repo.nextAssignmentID = 1
	repo.nextTaskID = 1
	repo.nextSessionID = 1
	repo.nextInvoiceID = 1

	repo.companies = make(map[int64]Company)
	repo.assignments = make(map[int64]Assignment)
	repo.tasks = make(map[int64]Task)
	repo.sessions = make(map[int64]WorkSession)
	repo.invoices = make(map[int64]Invoice)
}

func (repo *MemoryRepository) Migrate() error { return nil }

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

func (repo *MemoryRepository) InsertCompany(c Company) (*Company, error) {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	c.ID = repo.nextCompanyID
	repo.nextCompanyID++
	c.CreatedAt = time.Now()
	repo.companies[c.ID] = c
	return copyCompany(c), nil
}

func (repo *MemoryRepository) AllCompanies() ([]Company, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	all := make([]Company, 0, len(repo.companies))
	for _, c := range repo.companies {
		all = append(all, c)
	}
	sort.Slice(all, func(i, j int) bool {
		return strings.ToLower(all[i].Name) < strings.ToLower(all[j].Name)
	})
	return all, nil
}

func (repo *MemoryRepository) GetCompanyByID(id int64) (*Company, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	c, ok := repo.companies[id]
	if !ok {
		return nil, ErrNotFound
	}
	return copyCompany(c), nil
}

func (repo *MemoryRepository) UpdateCompany(id int64, updated Company) error {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	c, ok := repo.companies[id]
	if !ok || id == 0 {
		return ErrUpdateFailed
	}
	c.Name = updated.Name
	c.Address = updated.Address
	c.ContactEmail = updated.ContactEmail
	c.ContactPhone = updated.ContactPhone
	c.HourlyRate = updated.HourlyRate
	repo.companies[id] = c

	for assignmentID, a := range repo.assignments {
		if a.CompanyID == id {
			a.CompanyName = c.Name
			repo.assignments[assignmentID] = a
		}
	}
	return nil
}

func (repo *MemoryRepository) DeleteCompany(id int64) error {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	for _, a := range repo.assignments {
		if a.CompanyID == id {
			return ErrHasChildren
		}
	}
	if _, ok := repo.companies[id]; !ok {
		return ErrDeleteFailed
	}
	delete(repo.companies, id)
	return nil
}

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

func (repo *MemoryRepository) InsertAssignment(a Assignment) (*Assignment, error) {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	company, ok := repo.companies[a.CompanyID]
	if !ok {
		return nil, ErrNotFound
	}
	a.ID = repo.nextAssignmentID
	repo.nextAssignmentID++
	a.CompanyName = company.Name
	if a.Status == "" {
		a.Status = "active"
	}
	a.CreatedAt = time.Now()
	repo.assignments[a.ID] = a
	return copyAssignment(a), nil
}

func (repo *MemoryRepository) AssignmentsByCompany(companyID int64) ([]Assignment, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	all := make([]Assignment, 0)
	for _, a := range repo.assignments {
		if a.CompanyID == companyID {
			all = append(all, a)
		}
	}
	sort.Slice(all, func(i, j int) bool {
		return all[i].CreatedAt.After(all[j].CreatedAt)
	})
	return all, nil
}

func (repo *MemoryRepository) AllActiveAssignments() ([]Assignment, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	all := make([]Assignment, 0)
	for _, a := range repo.assignments {
		if a.Status == "active" {
			all = append(all, a)
		}
	}
	sort.Slice(all, func(i, j int) bool {
		if strings.ToLower(all[i].CompanyName) == strings.ToLower(all[j].CompanyName) {
			return strings.ToLower(all[i].Name) < strings.ToLower(all[j].Name)
		}
		return strings.ToLower(all[i].CompanyName) < strings.ToLower(all[j].CompanyName)
	})
	return all, nil
}

func (repo *MemoryRepository) GetAssignmentByID(id int64) (*Assignment, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	a, ok := repo.assignments[id]
	if !ok {
		return nil, ErrNotFound
	}
	return copyAssignment(a), nil
}

func (repo *MemoryRepository) UpdateAssignment(id int64, updated Assignment) error {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	a, ok := repo.assignments[id]
	if !ok || id == 0 {
		return ErrUpdateFailed
	}
	a.Name = updated.Name
	a.Description = updated.Description
	a.HourlyRate = updated.HourlyRate
	if updated.Status != "" {
		a.Status = updated.Status
	}
	repo.assignments[id] = a
	return nil
}

func (repo *MemoryRepository) DeleteAssignment(id int64) error {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	for _, t := range repo.tasks {
		if t.AssignmentID == id {
			return ErrHasChildren
		}
	}
	if _, ok := repo.assignments[id]; !ok {
		return ErrDeleteFailed
	}
	for sessionID, s := range repo.sessions {
		if s.AssignmentID == id {
			delete(repo.sessions, sessionID)
		}
	}
	delete(repo.assignments, id)
	return nil
}

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

func (repo *MemoryRepository) InsertTask(t Task) (*Task, error) {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	if _, ok := repo.assignments[t.AssignmentID]; !ok {
		return nil, ErrNotFound
	}
	maxNum := 0
	for _, existing := range repo.tasks {
		if existing.AssignmentID == t.AssignmentID && existing.Number > maxNum {
			maxNum = existing.Number
		}
	}
	t.ID = repo.nextTaskID
	repo.nextTaskID++
	t.Number = maxNum + 1
	repo.tasks[t.ID] = t
	withTotals := repo.taskWithTotalLocked(t)
	return copyTask(withTotals), nil
}

func (repo *MemoryRepository) TasksByAssignment(assignmentID int64) ([]Task, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	all := make([]Task, 0)
	for _, t := range repo.tasks {
		if t.AssignmentID == assignmentID {
			all = append(all, repo.taskWithTotalLocked(t))
		}
	}
	sort.Slice(all, func(i, j int) bool { return all[i].Number < all[j].Number })
	return all, nil
}

func (repo *MemoryRepository) GetTaskByID(id int64) (*Task, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	t, ok := repo.tasks[id]
	if !ok {
		return nil, ErrNotFound
	}
	withTotals := repo.taskWithTotalLocked(t)
	return copyTask(withTotals), nil
}

func (repo *MemoryRepository) UpdateTask(id int64, name string) error {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	t, ok := repo.tasks[id]
	if !ok || id == 0 || name == "" {
		return ErrUpdateFailed
	}
	t.Name = name
	repo.tasks[id] = t

	newDesc := fmt.Sprintf("%d - %s", t.Number, t.Name)
	for sessionID, s := range repo.sessions {
		if s.TaskID == id {
			s.TaskDescription = newDesc
			repo.sessions[sessionID] = s
		}
	}
	return nil
}

func (repo *MemoryRepository) DeleteTask(id int64) error {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	for _, s := range repo.sessions {
		if s.TaskID == id {
			return ErrHasChildren
		}
	}
	if _, ok := repo.tasks[id]; !ok {
		return ErrDeleteFailed
	}
	delete(repo.tasks, id)
	return nil
}

func (repo *MemoryRepository) TotalMinutesForTask(taskID int64) (float64, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	if _, ok := repo.tasks[taskID]; !ok {
		return 0, ErrNotFound
	}
	return repo.totalMinutesForTaskLocked(taskID), nil
}

// ── Work Sessions ──────────────────────────────────────────────────────────────

func (repo *MemoryRepository) InsertWorkSession(s WorkSession) (*WorkSession, error) {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	if _, ok := repo.assignments[s.AssignmentID]; !ok {
		return nil, ErrNotFound
	}
	if _, ok := repo.tasks[s.TaskID]; !ok {
		return nil, ErrNotFound
	}
	s.ID = repo.nextSessionID
	repo.nextSessionID++
	s.StartTime = time.Now()
	s.EndTime = nil
	s.DurationMinutes = 0
	repo.sessions[s.ID] = s
	return copyWorkSession(s), nil
}

func (repo *MemoryRepository) StopWorkSession(id int64) error {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	s, ok := repo.sessions[id]
	if !ok {
		return ErrUpdateFailed
	}
	now := time.Now()
	s.EndTime = &now
	s.DurationMinutes = now.Sub(s.StartTime).Minutes()
	repo.sessions[id] = s
	return nil
}

func (repo *MemoryRepository) InsertManualWorkSession(s WorkSession) (*WorkSession, error) {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	if s.EndTime == nil {
		return nil, ErrUpdateFailed
	}
	if _, ok := repo.assignments[s.AssignmentID]; !ok {
		return nil, ErrNotFound
	}
	if _, ok := repo.tasks[s.TaskID]; !ok {
		return nil, ErrNotFound
	}
	s.ID = repo.nextSessionID
	repo.nextSessionID++
	end := *s.EndTime
	s.EndTime = &end
	s.DurationMinutes = end.Sub(s.StartTime).Minutes()
	repo.sessions[s.ID] = s
	return copyWorkSession(s), nil
}

func (repo *MemoryRepository) UpdateWorkSession(id int64, updated WorkSession) error {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	s, ok := repo.sessions[id]
	if !ok || id == 0 {
		return ErrUpdateFailed
	}
	if _, ok := repo.tasks[updated.TaskID]; !ok {
		return ErrNotFound
	}
	s.TaskID = updated.TaskID
	s.TaskDescription = updated.TaskDescription
	s.StartTime = updated.StartTime
	if updated.EndTime != nil {
		end := *updated.EndTime
		s.EndTime = &end
		s.DurationMinutes = end.Sub(updated.StartTime).Minutes()
	} else {
		s.EndTime = nil
		s.DurationMinutes = 0
	}
	repo.sessions[id] = s
	return nil
}

func (repo *MemoryRepository) DeleteWorkSession(id int64) error {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	if _, ok := repo.sessions[id]; !ok {
		return ErrDeleteFailed
	}
	delete(repo.sessions, id)
	return nil
}

func (repo *MemoryRepository) WorkSessionsByAssignment(assignmentID int64, limit int) ([]WorkSession, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	all := make([]WorkSession, 0)
	for _, s := range repo.sessions {
		if s.AssignmentID == assignmentID {
			all = append(all, *copyWorkSession(s))
		}
	}
	sort.Slice(all, func(i, j int) bool { return all[i].StartTime.After(all[j].StartTime) })
	if limit > 0 && len(all) > limit {
		all = all[:limit]
	}
	return all, nil
}

func (repo *MemoryRepository) TotalHoursForAssignment(assignmentID int64) (float64, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	if _, ok := repo.assignments[assignmentID]; !ok {
		return 0, ErrNotFound
	}
	var minutes float64
	for _, s := range repo.sessions {
		if s.AssignmentID == assignmentID && s.EndTime != nil {
			minutes += s.DurationMinutes
		}
	}
	return minutes / 60.0, nil
}

// ── Invoices ───────────────────────────────────────────────────────────────────

func (repo *MemoryRepository) NextInvoiceNumber(date time.Time) (string, error) {
	repo.mu.RLock()
	defer repo.mu.RUnlock()

	prefix := date.Format("2006-01-02")
	count := 0
	for _, inv := range repo.invoices {
		if strings.HasPrefix(inv.InvoiceNumber, prefix+"_") {
			count++
		}
	}
	return fmt.Sprintf("%s_%d", prefix, count+1), nil
}

func (repo *MemoryRepository) InsertInvoice(inv Invoice) (*Invoice, error) {
	repo.mu.Lock()
	defer repo.mu.Unlock()

	if _, ok := repo.assignments[inv.AssignmentID]; !ok {
		return nil, ErrNotFound
	}
	for _, existing := range repo.invoices {
		if existing.InvoiceNumber == inv.InvoiceNumber {
			return nil, fmt.Errorf("invoice number already exists")
		}
	}
	inv.ID = repo.nextInvoiceID
	repo.nextInvoiceID++
	inv.GeneratedAt = time.Now()
	repo.invoices[inv.ID] = inv
	return copyInvoice(inv), nil
}

// ── Helpers ────────────────────────────────────────────────────────────────────

func (repo *MemoryRepository) taskWithTotalLocked(t Task) Task {
	t.TotalMinutes = repo.totalMinutesForTaskLocked(t.ID)
	return t
}

func (repo *MemoryRepository) totalMinutesForTaskLocked(taskID int64) float64 {
	var total float64
	for _, s := range repo.sessions {
		if s.TaskID == taskID && s.EndTime != nil {
			total += s.DurationMinutes
		}
	}
	return total
}

func copyCompany(c Company) *Company {
	v := c
	return &v
}

func copyAssignment(a Assignment) *Assignment {
	v := a
	return &v
}

func copyTask(t Task) *Task {
	v := t
	return &v
}

func copyWorkSession(s WorkSession) *WorkSession {
	v := s
	if s.EndTime != nil {
		end := *s.EndTime
		v.EndTime = &end
	}
	return &v
}

func copyInvoice(inv Invoice) *Invoice {
	v := inv
	return &v
}
