package services

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/breakpilot/billing-service/internal/database"
	"github.com/breakpilot/billing-service/internal/models"
	"github.com/google/uuid"
)

var (
	// ErrTaskLimitReached is returned when task balance is 0
	ErrTaskLimitReached = errors.New("TASK_LIMIT_REACHED")
	// ErrNoSubscription is returned when user has no subscription
	ErrNoSubscription = errors.New("NO_SUBSCRIPTION")
)

// TaskService handles task consumption and balance management
type TaskService struct {
	db         *database.DB
	subService *SubscriptionService
}

// NewTaskService creates a new TaskService
func NewTaskService(db *database.DB, subService *SubscriptionService) *TaskService {
	return &TaskService{
		db:         db,
		subService: subService,
	}
}

// GetAccountUsage retrieves or creates account usage for a user
func (s *TaskService) GetAccountUsage(ctx context.Context, userID uuid.UUID) (*models.AccountUsage, error) {
	query := `
		SELECT id, account_id, plan, monthly_task_allowance, carryover_months_cap,
		       max_task_balance, task_balance, last_renewal_at, created_at, updated_at
		FROM account_usage
		WHERE account_id = $1
	`

	var usage models.AccountUsage
	err := s.db.Pool.QueryRow(ctx, query, userID).Scan(
		&usage.ID, &usage.AccountID, &usage.PlanID, &usage.MonthlyTaskAllowance,
		&usage.CarryoverMonthsCap, &usage.MaxTaskBalance, &usage.TaskBalance,
		&usage.LastRenewalAt, &usage.CreatedAt, &usage.UpdatedAt,
	)

	if err != nil {
		if err.Error() == "no rows in result set" {
			// Create new account usage based on subscription
			return s.createAccountUsage(ctx, userID)
		}
		return nil, err
	}

	// Check if month renewal is needed
	if err := s.checkAndApplyMonthRenewal(ctx, &usage); err != nil {
		return nil, err
	}

	return &usage, nil
}

// createAccountUsage creates account usage based on user's subscription
func (s *TaskService) createAccountUsage(ctx context.Context, userID uuid.UUID) (*models.AccountUsage, error) {
	// Get subscription to determine plan
	sub, err := s.subService.GetByUserID(ctx, userID)
	if err != nil || sub == nil {
		return nil, ErrNoSubscription
	}

	// Get plan features
	plan, err := s.subService.GetPlanByID(ctx, string(sub.PlanID))
	if err != nil || plan == nil {
		return nil, fmt.Errorf("plan not found: %s", sub.PlanID)
	}

	now := time.Now()
	usage := &models.AccountUsage{
		AccountID:            userID,
		PlanID:               sub.PlanID,
		MonthlyTaskAllowance: plan.Features.MonthlyTaskAllowance,
		CarryoverMonthsCap:   models.CarryoverMonthsCap,
		MaxTaskBalance:       plan.Features.MaxTaskBalance,
		TaskBalance:          plan.Features.MonthlyTaskAllowance, // Start with one month's worth
		LastRenewalAt:        now,
	}

	query := `
		INSERT INTO account_usage (
			account_id, plan, monthly_task_allowance, carryover_months_cap,
			max_task_balance, task_balance, last_renewal_at
		) VALUES ($1, $2, $3, $4, $5, $6, $7)
		RETURNING id, created_at, updated_at
	`

	err = s.db.Pool.QueryRow(ctx, query,
		usage.AccountID, usage.PlanID, usage.MonthlyTaskAllowance,
		usage.CarryoverMonthsCap, usage.MaxTaskBalance, usage.TaskBalance, usage.LastRenewalAt,
	).Scan(&usage.ID, &usage.CreatedAt, &usage.UpdatedAt)

	if err != nil {
		return nil, err
	}

	return usage, nil
}

// checkAndApplyMonthRenewal checks if a month has passed and adds allowance
// Implements the carryover logic: tasks accumulate up to max_task_balance
func (s *TaskService) checkAndApplyMonthRenewal(ctx context.Context, usage *models.AccountUsage) error {
	now := time.Now()

	// Check if at least one month has passed since last renewal
	monthsSinceRenewal := monthsBetween(usage.LastRenewalAt, now)
	if monthsSinceRenewal < 1 {
		return nil
	}

	// Calculate new balance with carryover
	// Add monthly allowance for each month that passed
	newBalance := usage.TaskBalance
	for i := 0; i < monthsSinceRenewal; i++ {
		newBalance += usage.MonthlyTaskAllowance
		// Cap at max balance
		if newBalance > usage.MaxTaskBalance {
			newBalance = usage.MaxTaskBalance
			break
		}
	}

	// Calculate new renewal date (add the number of months)
	newRenewalAt := usage.LastRenewalAt.AddDate(0, monthsSinceRenewal, 0)

	// Update in database
	query := `
		UPDATE account_usage
		SET task_balance = $2, last_renewal_at = $3, updated_at = NOW()
		WHERE id = $1
	`
	_, err := s.db.Pool.Exec(ctx, query, usage.ID, newBalance, newRenewalAt)
	if err != nil {
		return err
	}

	// Update local struct
	usage.TaskBalance = newBalance
	usage.LastRenewalAt = newRenewalAt

	return nil
}

// monthsBetween calculates full months between two dates
func monthsBetween(start, end time.Time) int {
	months := 0
	for start.AddDate(0, months+1, 0).Before(end) || start.AddDate(0, months+1, 0).Equal(end) {
		months++
	}
	return months
}

// CheckTaskAllowed checks if a task can be consumed (balance > 0)
func (s *TaskService) CheckTaskAllowed(ctx context.Context, userID uuid.UUID) (*models.CheckTaskAllowedResponse, error) {
	usage, err := s.GetAccountUsage(ctx, userID)
	if err != nil {
		if errors.Is(err, ErrNoSubscription) {
			return &models.CheckTaskAllowedResponse{
				Allowed: false,
				PlanID:  "",
				Message: "Kein aktives Abonnement gefunden.",
			}, nil
		}
		return nil, err
	}

	// Premium Fair Use mode - always allow
	plan, _ := s.subService.GetPlanByID(ctx, string(usage.PlanID))
	if plan != nil && plan.Features.FairUseMode {
		return &models.CheckTaskAllowedResponse{
			Allowed:        true,
			TasksAvailable: usage.TaskBalance,
			MaxTasks:       usage.MaxTaskBalance,
			PlanID:         usage.PlanID,
		}, nil
	}

	allowed := usage.TaskBalance > 0

	response := &models.CheckTaskAllowedResponse{
		Allowed:        allowed,
		TasksAvailable: usage.TaskBalance,
		MaxTasks:       usage.MaxTaskBalance,
		PlanID:         usage.PlanID,
	}

	if !allowed {
		response.Message = "Dein Aufgaben-Kontingent ist aufgebraucht."
	}

	return response, nil
}

// ConsumeTask consumes one task from the balance
// Returns error if balance is 0
func (s *TaskService) ConsumeTask(ctx context.Context, userID uuid.UUID, taskType models.TaskType) (*models.ConsumeTaskResponse, error) {
	// First check if allowed
	checkResponse, err := s.CheckTaskAllowed(ctx, userID)
	if err != nil {
		return nil, err
	}

	if !checkResponse.Allowed {
		return &models.ConsumeTaskResponse{
			Success:        false,
			TasksRemaining: 0,
			Message:        checkResponse.Message,
		}, ErrTaskLimitReached
	}

	// Get current usage
	usage, err := s.GetAccountUsage(ctx, userID)
	if err != nil {
		return nil, err
	}

	// Start transaction
	tx, err := s.db.Pool.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	// Decrement balance (only if not Premium Fair Use)
	plan, _ := s.subService.GetPlanByID(ctx, string(usage.PlanID))
	newBalance := usage.TaskBalance
	if plan == nil || !plan.Features.FairUseMode {
		newBalance = usage.TaskBalance - 1
		_, err = tx.Exec(ctx, `
			UPDATE account_usage
			SET task_balance = $2, updated_at = NOW()
			WHERE account_id = $1
		`, userID, newBalance)
		if err != nil {
			return nil, err
		}
	}

	// Create task record
	taskID := uuid.New()
	_, err = tx.Exec(ctx, `
		INSERT INTO tasks (id, account_id, task_type, consumed, created_at)
		VALUES ($1, $2, $3, true, NOW())
	`, taskID, userID, taskType)
	if err != nil {
		return nil, err
	}

	// Commit transaction
	if err = tx.Commit(ctx); err != nil {
		return nil, err
	}

	return &models.ConsumeTaskResponse{
		Success:        true,
		TaskID:         taskID.String(),
		TasksRemaining: newBalance,
	}, nil
}

// GetTaskUsageInfo returns formatted task usage info for display
func (s *TaskService) GetTaskUsageInfo(ctx context.Context, userID uuid.UUID) (*models.TaskUsageInfo, error) {
	usage, err := s.GetAccountUsage(ctx, userID)
	if err != nil {
		return nil, err
	}

	// Check for Fair Use mode (Premium)
	plan, _ := s.subService.GetPlanByID(ctx, string(usage.PlanID))
	if plan != nil && plan.Features.FairUseMode {
		return &models.TaskUsageInfo{
			TasksAvailable: usage.TaskBalance,
			MaxTasks:       usage.MaxTaskBalance,
			InfoText:       "Unbegrenzte Aufgaben (Fair Use)",
			TooltipText:    "Im Premium-Tarif gibt es keine praktische Begrenzung.",
		}, nil
	}

	return &models.TaskUsageInfo{
		TasksAvailable: usage.TaskBalance,
		MaxTasks:       usage.MaxTaskBalance,
		InfoText:       fmt.Sprintf("Aufgaben verfuegbar: %d von max. %d", usage.TaskBalance, usage.MaxTaskBalance),
		TooltipText:    "Aufgaben koennen sich bis zu 5 Monate ansammeln.",
	}, nil
}

// UpdatePlanForUser updates the plan and adjusts allowances
func (s *TaskService) UpdatePlanForUser(ctx context.Context, userID uuid.UUID, newPlanID models.PlanID) error {
	plan, err := s.subService.GetPlanByID(ctx, string(newPlanID))
	if err != nil || plan == nil {
		return fmt.Errorf("plan not found: %s", newPlanID)
	}

	// Update account usage with new plan limits
	query := `
		UPDATE account_usage
		SET plan = $2,
		    monthly_task_allowance = $3,
		    max_task_balance = $4,
		    updated_at = NOW()
		WHERE account_id = $1
	`

	_, err = s.db.Pool.Exec(ctx, query,
		userID, newPlanID, plan.Features.MonthlyTaskAllowance, plan.Features.MaxTaskBalance)
	return err
}

// GetTaskHistory returns task history for a user
func (s *TaskService) GetTaskHistory(ctx context.Context, userID uuid.UUID, limit int) ([]models.Task, error) {
	if limit <= 0 {
		limit = 50
	}

	query := `
		SELECT id, account_id, task_type, created_at, consumed
		FROM tasks
		WHERE account_id = $1
		ORDER BY created_at DESC
		LIMIT $2
	`

	rows, err := s.db.Pool.Query(ctx, query, userID, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var tasks []models.Task
	for rows.Next() {
		var task models.Task
		err := rows.Scan(&task.ID, &task.AccountID, &task.TaskType, &task.CreatedAt, &task.Consumed)
		if err != nil {
			return nil, err
		}
		tasks = append(tasks, task)
	}

	return tasks, nil
}
