package services

import (
	"context"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgxpool"
)

// DeadlineService handles consent deadlines and account suspensions
type DeadlineService struct {
	pool                *pgxpool.Pool
	notificationService *NotificationService
}

// ConsentDeadline represents a consent deadline for a user
type ConsentDeadline struct {
	ID                uuid.UUID  `json:"id"`
	UserID            uuid.UUID  `json:"user_id"`
	DocumentVersionID uuid.UUID  `json:"document_version_id"`
	DeadlineAt        time.Time  `json:"deadline_at"`
	ReminderCount     int        `json:"reminder_count"`
	LastReminderAt    *time.Time `json:"last_reminder_at"`
	ConsentGivenAt    *time.Time `json:"consent_given_at"`
	CreatedAt         time.Time  `json:"created_at"`
	// Joined fields
	DocumentName  string `json:"document_name"`
	VersionNumber string `json:"version_number"`
}

// AccountSuspension represents an account suspension
type AccountSuspension struct {
	ID          uuid.UUID              `json:"id"`
	UserID      uuid.UUID              `json:"user_id"`
	Reason      string                 `json:"reason"`
	Details     map[string]interface{} `json:"details"`
	SuspendedAt time.Time              `json:"suspended_at"`
	LiftedAt    *time.Time             `json:"lifted_at"`
	LiftedBy    *uuid.UUID             `json:"lifted_by"`
}

// NewDeadlineService creates a new deadline service
func NewDeadlineService(pool *pgxpool.Pool, notificationService *NotificationService) *DeadlineService {
	return &DeadlineService{
		pool:                pool,
		notificationService: notificationService,
	}
}

// CreateDeadlinesForPublishedVersion creates consent deadlines for all active users
// when a new mandatory document version is published
func (s *DeadlineService) CreateDeadlinesForPublishedVersion(ctx context.Context, versionID uuid.UUID) error {
	// Get version info
	var documentName, versionNumber string
	var isMandatory bool
	err := s.pool.QueryRow(ctx, `
		SELECT ld.name, dv.version, ld.is_mandatory
		FROM document_versions dv
		JOIN legal_documents ld ON dv.document_id = ld.id
		WHERE dv.id = $1
	`, versionID).Scan(&documentName, &versionNumber, &isMandatory)
	if err != nil {
		return fmt.Errorf("failed to get version info: %w", err)
	}

	// Only create deadlines for mandatory documents
	if !isMandatory {
		return nil
	}

	// Deadline is 30 days from now
	deadlineAt := time.Now().AddDate(0, 0, 30)

	// Get all active users who haven't given consent to this version
	_, err = s.pool.Exec(ctx, `
		INSERT INTO consent_deadlines (user_id, document_version_id, deadline_at)
		SELECT u.id, $1, $2
		FROM users u
		WHERE u.account_status = 'active'
		AND NOT EXISTS (
			SELECT 1 FROM user_consents uc
			WHERE uc.user_id = u.id AND uc.document_version_id = $1 AND uc.consented = TRUE
		)
		ON CONFLICT (user_id, document_version_id) DO NOTHING
	`, versionID, deadlineAt)

	if err != nil {
		return fmt.Errorf("failed to create deadlines: %w", err)
	}

	// Notify users via notification service
	if s.notificationService != nil {
		go s.notificationService.NotifyConsentRequired(ctx, documentName, versionID.String())
	}

	return nil
}

// MarkConsentGiven marks a deadline as fulfilled when user gives consent
func (s *DeadlineService) MarkConsentGiven(ctx context.Context, userID, versionID uuid.UUID) error {
	_, err := s.pool.Exec(ctx, `
		UPDATE consent_deadlines
		SET consent_given_at = NOW()
		WHERE user_id = $1 AND document_version_id = $2 AND consent_given_at IS NULL
	`, userID, versionID)

	if err != nil {
		return err
	}

	// Check if user should be unsuspended
	return s.checkAndLiftSuspension(ctx, userID)
}

// GetPendingDeadlines returns all pending deadlines for a user
func (s *DeadlineService) GetPendingDeadlines(ctx context.Context, userID uuid.UUID) ([]ConsentDeadline, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT cd.id, cd.user_id, cd.document_version_id, cd.deadline_at,
		       cd.reminder_count, cd.last_reminder_at, cd.consent_given_at, cd.created_at,
		       ld.name as document_name, dv.version as version_number
		FROM consent_deadlines cd
		JOIN document_versions dv ON cd.document_version_id = dv.id
		JOIN legal_documents ld ON dv.document_id = ld.id
		WHERE cd.user_id = $1 AND cd.consent_given_at IS NULL
		ORDER BY cd.deadline_at ASC
	`, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var deadlines []ConsentDeadline
	for rows.Next() {
		var d ConsentDeadline
		if err := rows.Scan(&d.ID, &d.UserID, &d.DocumentVersionID, &d.DeadlineAt,
			&d.ReminderCount, &d.LastReminderAt, &d.ConsentGivenAt, &d.CreatedAt,
			&d.DocumentName, &d.VersionNumber); err != nil {
			continue
		}
		deadlines = append(deadlines, d)
	}

	return deadlines, nil
}

// ProcessDailyDeadlines is meant to be called by a cron job daily
// It sends reminders and suspends accounts that have missed deadlines
func (s *DeadlineService) ProcessDailyDeadlines(ctx context.Context) error {
	now := time.Now()

	// 1. Send reminders for upcoming deadlines
	if err := s.sendReminders(ctx, now); err != nil {
		fmt.Printf("Error sending reminders: %v\n", err)
	}

	// 2. Suspend accounts with expired deadlines
	if err := s.suspendExpiredAccounts(ctx, now); err != nil {
		fmt.Printf("Error suspending accounts: %v\n", err)
	}

	return nil
}

// sendReminders sends reminder notifications based on days remaining
func (s *DeadlineService) sendReminders(ctx context.Context, now time.Time) error {
	// Reminder schedule: Day 7, 14, 21, 28
	reminderDays := []int{7, 14, 21, 28}

	for _, days := range reminderDays {
		targetDate := now.AddDate(0, 0, days)
		dayStart := time.Date(targetDate.Year(), targetDate.Month(), targetDate.Day(), 0, 0, 0, 0, targetDate.Location())
		dayEnd := dayStart.AddDate(0, 0, 1)

		// Find deadlines that fall on this reminder day
		rows, err := s.pool.Query(ctx, `
			SELECT cd.id, cd.user_id, cd.document_version_id, cd.deadline_at, cd.reminder_count,
			       ld.name as document_name
			FROM consent_deadlines cd
			JOIN document_versions dv ON cd.document_version_id = dv.id
			JOIN legal_documents ld ON dv.document_id = ld.id
			WHERE cd.consent_given_at IS NULL
			AND cd.deadline_at >= $1 AND cd.deadline_at < $2
			AND (cd.last_reminder_at IS NULL OR cd.last_reminder_at < $3)
		`, dayStart, dayEnd, dayStart)

		if err != nil {
			continue
		}

		for rows.Next() {
			var id, userID, versionID uuid.UUID
			var deadlineAt time.Time
			var reminderCount int
			var documentName string

			if err := rows.Scan(&id, &userID, &versionID, &deadlineAt, &reminderCount, &documentName); err != nil {
				continue
			}

			// Send reminder notification
			daysLeft := 30 - (30 - days)
			urgency := "freundlich"
			if days <= 7 {
				urgency = "dringend"
			} else if days <= 14 {
				urgency = "wichtig"
			}

			title := fmt.Sprintf("Erinnerung: Zustimmung erforderlich (%s)", urgency)
			body := fmt.Sprintf("Bitte bestätigen Sie '%s' innerhalb von %d Tagen.", documentName, daysLeft)

			if s.notificationService != nil {
				s.notificationService.CreateNotification(ctx, userID, NotificationTypeConsentReminder, title, body, map[string]interface{}{
					"document_name": documentName,
					"days_left":     daysLeft,
					"version_id":    versionID.String(),
				})
			}

			// Update reminder count and timestamp
			s.pool.Exec(ctx, `
				UPDATE consent_deadlines
				SET reminder_count = reminder_count + 1, last_reminder_at = NOW()
				WHERE id = $1
			`, id)
		}
		rows.Close()
	}

	return nil
}

// suspendExpiredAccounts suspends accounts that have missed their deadline
func (s *DeadlineService) suspendExpiredAccounts(ctx context.Context, now time.Time) error {
	// Find users with expired deadlines
	rows, err := s.pool.Query(ctx, `
		SELECT DISTINCT cd.user_id, array_agg(ld.name) as documents
		FROM consent_deadlines cd
		JOIN document_versions dv ON cd.document_version_id = dv.id
		JOIN legal_documents ld ON dv.document_id = ld.id
		JOIN users u ON cd.user_id = u.id
		WHERE cd.consent_given_at IS NULL
		AND cd.deadline_at < $1
		AND u.account_status = 'active'
		AND ld.is_mandatory = TRUE
		GROUP BY cd.user_id
	`, now)

	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		var userID uuid.UUID
		var documents []string

		if err := rows.Scan(&userID, &documents); err != nil {
			continue
		}

		// Suspend the account
		if err := s.suspendAccount(ctx, userID, "consent_deadline_missed", documents); err != nil {
			fmt.Printf("Failed to suspend user %s: %v\n", userID, err)
		}
	}

	return nil
}

// suspendAccount suspends a user account
func (s *DeadlineService) suspendAccount(ctx context.Context, userID uuid.UUID, reason string, documents []string) error {
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return err
	}
	defer tx.Rollback(ctx)

	// Update user status
	_, err = tx.Exec(ctx, `
		UPDATE users SET account_status = 'suspended', updated_at = NOW()
		WHERE id = $1 AND account_status = 'active'
	`, userID)
	if err != nil {
		return err
	}

	// Create suspension record
	_, err = tx.Exec(ctx, `
		INSERT INTO account_suspensions (user_id, reason, details)
		VALUES ($1, $2, $3)
	`, userID, reason, map[string]interface{}{"documents": documents})
	if err != nil {
		return err
	}

	// Log to audit
	_, err = tx.Exec(ctx, `
		INSERT INTO consent_audit_log (user_id, action, entity_type, entity_id, details)
		VALUES ($1, 'account_suspended', 'user', $1, $2)
	`, userID, map[string]interface{}{"reason": reason, "documents": documents})
	if err != nil {
		return err
	}

	if err := tx.Commit(ctx); err != nil {
		return err
	}

	// Send suspension notification
	if s.notificationService != nil {
		title := "Account vorübergehend gesperrt"
		body := "Ihr Account wurde gesperrt, da ausstehende Zustimmungen nicht innerhalb der Frist erteilt wurden. Bitte bestätigen Sie die ausstehenden Dokumente."
		s.notificationService.CreateNotification(ctx, userID, NotificationTypeAccountSuspended, title, body, map[string]interface{}{
			"documents": documents,
		})
	}

	return nil
}

// checkAndLiftSuspension checks if user has completed all required consents and lifts suspension
func (s *DeadlineService) checkAndLiftSuspension(ctx context.Context, userID uuid.UUID) error {
	// Check if user is currently suspended
	var accountStatus string
	err := s.pool.QueryRow(ctx, `SELECT account_status FROM users WHERE id = $1`, userID).Scan(&accountStatus)
	if err != nil || accountStatus != "suspended" {
		return nil
	}

	// Check if there are any pending mandatory consents
	var pendingCount int
	err = s.pool.QueryRow(ctx, `
		SELECT COUNT(*)
		FROM consent_deadlines cd
		JOIN document_versions dv ON cd.document_version_id = dv.id
		JOIN legal_documents ld ON dv.document_id = ld.id
		WHERE cd.user_id = $1
		AND cd.consent_given_at IS NULL
		AND ld.is_mandatory = TRUE
	`, userID).Scan(&pendingCount)

	if err != nil {
		return err
	}

	// If no pending consents, lift the suspension
	if pendingCount == 0 {
		return s.liftSuspension(ctx, userID)
	}

	return nil
}

// liftSuspension lifts a user's suspension
func (s *DeadlineService) liftSuspension(ctx context.Context, userID uuid.UUID) error {
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return err
	}
	defer tx.Rollback(ctx)

	// Update user status
	_, err = tx.Exec(ctx, `
		UPDATE users SET account_status = 'active', updated_at = NOW()
		WHERE id = $1 AND account_status = 'suspended'
	`, userID)
	if err != nil {
		return err
	}

	// Update suspension record
	_, err = tx.Exec(ctx, `
		UPDATE account_suspensions
		SET lifted_at = NOW()
		WHERE user_id = $1 AND lifted_at IS NULL
	`, userID)
	if err != nil {
		return err
	}

	// Log to audit
	_, err = tx.Exec(ctx, `
		INSERT INTO consent_audit_log (user_id, action, entity_type, entity_id)
		VALUES ($1, 'account_restored', 'user', $1)
	`, userID)
	if err != nil {
		return err
	}

	if err := tx.Commit(ctx); err != nil {
		return err
	}

	// Send restoration notification
	if s.notificationService != nil {
		title := "Account wiederhergestellt"
		body := "Vielen Dank! Ihr Account wurde wiederhergestellt. Sie können die Anwendung wieder vollständig nutzen."
		s.notificationService.CreateNotification(ctx, userID, NotificationTypeAccountRestored, title, body, nil)
	}

	return nil
}

// GetAccountSuspension returns the current suspension for a user
func (s *DeadlineService) GetAccountSuspension(ctx context.Context, userID uuid.UUID) (*AccountSuspension, error) {
	var suspension AccountSuspension
	err := s.pool.QueryRow(ctx, `
		SELECT id, user_id, reason, details, suspended_at, lifted_at, lifted_by
		FROM account_suspensions
		WHERE user_id = $1 AND lifted_at IS NULL
		ORDER BY suspended_at DESC
		LIMIT 1
	`, userID).Scan(&suspension.ID, &suspension.UserID, &suspension.Reason, &suspension.Details,
		&suspension.SuspendedAt, &suspension.LiftedAt, &suspension.LiftedBy)

	if err != nil {
		return nil, err
	}

	return &suspension, nil
}

// IsUserSuspended checks if a user is currently suspended
func (s *DeadlineService) IsUserSuspended(ctx context.Context, userID uuid.UUID) (bool, error) {
	var status string
	err := s.pool.QueryRow(ctx, `SELECT account_status FROM users WHERE id = $1`, userID).Scan(&status)
	if err != nil {
		return false, err
	}
	return status == "suspended", nil
}
