package services

import (
	"context"
	"database/sql"
	"fmt"

	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"

	"mgit.msbls.de/m/KanzlAI-mGMT/internal/models"
)

type TimeEntryService struct {
	db    *sqlx.DB
	audit *AuditService
}

func NewTimeEntryService(db *sqlx.DB, audit *AuditService) *TimeEntryService {
	return &TimeEntryService{db: db, audit: audit}
}

type CreateTimeEntryInput struct {
	CaseID          uuid.UUID `json:"case_id"`
	Date            string    `json:"date"`
	DurationMinutes int       `json:"duration_minutes"`
	Description     string    `json:"description"`
	Activity        *string   `json:"activity,omitempty"`
	Billable        *bool     `json:"billable,omitempty"`
	HourlyRate      *float64  `json:"hourly_rate,omitempty"`
}

type UpdateTimeEntryInput struct {
	Date            *string  `json:"date,omitempty"`
	DurationMinutes *int     `json:"duration_minutes,omitempty"`
	Description     *string  `json:"description,omitempty"`
	Activity        *string  `json:"activity,omitempty"`
	Billable        *bool    `json:"billable,omitempty"`
	HourlyRate      *float64 `json:"hourly_rate,omitempty"`
}

type TimeEntryFilter struct {
	CaseID *uuid.UUID
	UserID *uuid.UUID
	From   string
	To     string
	Limit  int
	Offset int
}

type TimeEntrySummary struct {
	GroupKey       string  `db:"group_key" json:"group_key"`
	TotalMinutes   int     `db:"total_minutes" json:"total_minutes"`
	BillableMinutes int   `db:"billable_minutes" json:"billable_minutes"`
	TotalAmount    float64 `db:"total_amount" json:"total_amount"`
	EntryCount     int     `db:"entry_count" json:"entry_count"`
}

const timeEntryCols = `id, tenant_id, case_id, user_id, date, duration_minutes, description,
	activity, billable, billed, invoice_id, hourly_rate, created_at, updated_at`

func (s *TimeEntryService) ListForCase(ctx context.Context, tenantID, caseID uuid.UUID) ([]models.TimeEntry, error) {
	var entries []models.TimeEntry
	err := s.db.SelectContext(ctx, &entries,
		`SELECT `+timeEntryCols+` FROM time_entries
		 WHERE tenant_id = $1 AND case_id = $2
		 ORDER BY date DESC, created_at DESC`,
		tenantID, caseID)
	if err != nil {
		return nil, fmt.Errorf("list time entries for case: %w", err)
	}
	return entries, nil
}

func (s *TimeEntryService) List(ctx context.Context, tenantID uuid.UUID, filter TimeEntryFilter) ([]models.TimeEntry, int, error) {
	if filter.Limit <= 0 {
		filter.Limit = 20
	}
	if filter.Limit > 100 {
		filter.Limit = 100
	}

	where := "WHERE tenant_id = $1"
	args := []any{tenantID}
	argIdx := 2

	if filter.CaseID != nil {
		where += fmt.Sprintf(" AND case_id = $%d", argIdx)
		args = append(args, *filter.CaseID)
		argIdx++
	}
	if filter.UserID != nil {
		where += fmt.Sprintf(" AND user_id = $%d", argIdx)
		args = append(args, *filter.UserID)
		argIdx++
	}
	if filter.From != "" {
		where += fmt.Sprintf(" AND date >= $%d", argIdx)
		args = append(args, filter.From)
		argIdx++
	}
	if filter.To != "" {
		where += fmt.Sprintf(" AND date <= $%d", argIdx)
		args = append(args, filter.To)
		argIdx++
	}

	var total int
	err := s.db.GetContext(ctx, &total,
		"SELECT COUNT(*) FROM time_entries "+where, args...)
	if err != nil {
		return nil, 0, fmt.Errorf("count time entries: %w", err)
	}

	query := fmt.Sprintf("SELECT %s FROM time_entries %s ORDER BY date DESC, created_at DESC LIMIT $%d OFFSET $%d",
		timeEntryCols, where, argIdx, argIdx+1)
	args = append(args, filter.Limit, filter.Offset)

	var entries []models.TimeEntry
	err = s.db.SelectContext(ctx, &entries, query, args...)
	if err != nil {
		return nil, 0, fmt.Errorf("list time entries: %w", err)
	}
	return entries, total, nil
}

func (s *TimeEntryService) GetByID(ctx context.Context, tenantID, entryID uuid.UUID) (*models.TimeEntry, error) {
	var entry models.TimeEntry
	err := s.db.GetContext(ctx, &entry,
		`SELECT `+timeEntryCols+` FROM time_entries WHERE tenant_id = $1 AND id = $2`,
		tenantID, entryID)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get time entry: %w", err)
	}
	return &entry, nil
}

func (s *TimeEntryService) Create(ctx context.Context, tenantID, userID uuid.UUID, input CreateTimeEntryInput) (*models.TimeEntry, error) {
	billable := true
	if input.Billable != nil {
		billable = *input.Billable
	}

	// If no hourly rate provided, look up the current billing rate
	hourlyRate := input.HourlyRate
	if hourlyRate == nil {
		var rate float64
		err := s.db.GetContext(ctx, &rate,
			`SELECT rate FROM billing_rates
			 WHERE tenant_id = $1 AND (user_id = $2 OR user_id IS NULL)
			 AND valid_from <= $3 AND (valid_to IS NULL OR valid_to >= $3)
			 ORDER BY user_id NULLS LAST LIMIT 1`,
			tenantID, userID, input.Date)
		if err == nil {
			hourlyRate = &rate
		}
	}

	var entry models.TimeEntry
	err := s.db.QueryRowxContext(ctx,
		`INSERT INTO time_entries (tenant_id, case_id, user_id, date, duration_minutes, description, activity, billable, hourly_rate)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
		 RETURNING `+timeEntryCols,
		tenantID, input.CaseID, userID, input.Date, input.DurationMinutes, input.Description, input.Activity, billable, hourlyRate,
	).StructScan(&entry)
	if err != nil {
		return nil, fmt.Errorf("create time entry: %w", err)
	}

	s.audit.Log(ctx, "create", "time_entry", &entry.ID, nil, entry)
	return &entry, nil
}

func (s *TimeEntryService) Update(ctx context.Context, tenantID, entryID uuid.UUID, input UpdateTimeEntryInput) (*models.TimeEntry, error) {
	old, err := s.GetByID(ctx, tenantID, entryID)
	if err != nil {
		return nil, err
	}
	if old == nil {
		return nil, fmt.Errorf("time entry not found")
	}
	if old.Billed {
		return nil, fmt.Errorf("cannot update a billed time entry")
	}

	var entry models.TimeEntry
	err = s.db.QueryRowxContext(ctx,
		`UPDATE time_entries SET
			date = COALESCE($3, date),
			duration_minutes = COALESCE($4, duration_minutes),
			description = COALESCE($5, description),
			activity = COALESCE($6, activity),
			billable = COALESCE($7, billable),
			hourly_rate = COALESCE($8, hourly_rate),
			updated_at = now()
		 WHERE tenant_id = $1 AND id = $2
		 RETURNING `+timeEntryCols,
		tenantID, entryID, input.Date, input.DurationMinutes, input.Description, input.Activity, input.Billable, input.HourlyRate,
	).StructScan(&entry)
	if err != nil {
		return nil, fmt.Errorf("update time entry: %w", err)
	}

	s.audit.Log(ctx, "update", "time_entry", &entry.ID, old, entry)
	return &entry, nil
}

func (s *TimeEntryService) Delete(ctx context.Context, tenantID, entryID uuid.UUID) error {
	old, err := s.GetByID(ctx, tenantID, entryID)
	if err != nil {
		return err
	}
	if old == nil {
		return fmt.Errorf("time entry not found")
	}
	if old.Billed {
		return fmt.Errorf("cannot delete a billed time entry")
	}

	_, err = s.db.ExecContext(ctx,
		`DELETE FROM time_entries WHERE tenant_id = $1 AND id = $2`,
		tenantID, entryID)
	if err != nil {
		return fmt.Errorf("delete time entry: %w", err)
	}

	s.audit.Log(ctx, "delete", "time_entry", &entryID, old, nil)
	return nil
}

func (s *TimeEntryService) Summary(ctx context.Context, tenantID uuid.UUID, groupBy string, from, to string) ([]TimeEntrySummary, error) {
	var groupExpr string
	switch groupBy {
	case "user":
		groupExpr = "user_id::text"
	case "month":
		groupExpr = "to_char(date, 'YYYY-MM')"
	default:
		groupExpr = "case_id::text"
	}

	where := "WHERE tenant_id = $1"
	args := []any{tenantID}
	argIdx := 2

	if from != "" {
		where += fmt.Sprintf(" AND date >= $%d", argIdx)
		args = append(args, from)
		argIdx++
	}
	if to != "" {
		where += fmt.Sprintf(" AND date <= $%d", argIdx)
		args = append(args, to)
		argIdx++
	}

	query := fmt.Sprintf(`SELECT %s AS group_key,
		SUM(duration_minutes) AS total_minutes,
		SUM(CASE WHEN billable THEN duration_minutes ELSE 0 END) AS billable_minutes,
		SUM(CASE WHEN billable AND hourly_rate IS NOT NULL THEN duration_minutes * hourly_rate / 60.0 ELSE 0 END) AS total_amount,
		COUNT(*) AS entry_count
		FROM time_entries %s
		GROUP BY %s
		ORDER BY %s`,
		groupExpr, where, groupExpr, groupExpr)

	var summaries []TimeEntrySummary
	err := s.db.SelectContext(ctx, &summaries, query, args...)
	if err != nil {
		return nil, fmt.Errorf("time entry summary: %w", err)
	}
	return summaries, nil
}
