package services

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgxpool"
	"gitlab.com/patrickbritton3/sojorn/go-backend/internal/models"
)

type AppealService struct {
	pool *pgxpool.Pool
}

func NewAppealService(pool *pgxpool.Pool) *AppealService {
	return &AppealService{pool: pool}
}

// CreateUserViolation creates a violation record when content is flagged
func (s *AppealService) CreateUserViolation(ctx context.Context, userID uuid.UUID, moderationFlagID uuid.UUID, flagReason string, scores map[string]float64) (*models.UserViolation, error) {
	scoresJSON, err := json.Marshal(scores)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal scores: %w", err)
	}

	var violation models.UserViolation
	query := `
		SELECT * FROM create_user_violation($1, $2, $3, $4)
	`

	err = s.pool.QueryRow(ctx, query, userID, moderationFlagID, flagReason, scoresJSON).Scan(
		&violation.ID,
		&violation.UserID,
		&violation.ModerationFlagID,
		&violation.ViolationType,
		&violation.ViolationReason,
		&violation.SeverityScore,
		&violation.IsAppealable,
		&violation.AppealDeadline,
		&violation.Status,
		&violation.CreatedAt,
		&violation.UpdatedAt,
	)

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

	return &violation, nil
}

// GetUserViolations returns all violations for a user
func (s *AppealService) GetUserViolations(ctx context.Context, userID uuid.UUID, limit, offset int) ([]models.UserViolationResponse, error) {
	query := `
		SELECT 
			uv.*,
			mf.flag_reason,
			COALESCE(p.body, '') as post_content,
			COALESCE(c.body, '') as comment_content,
			CASE 
				WHEN uv.is_appealable = true AND uv.appeal_deadline > NOW() AND uv.status = 'active' THEN true
				ELSE false
			END as can_appeal
		FROM user_violations uv
		LEFT JOIN moderation_flags mf ON uv.moderation_flag_id = mf.id
		LEFT JOIN posts p ON mf.post_id = p.id
		LEFT JOIN comments c ON mf.comment_id = c.id
		WHERE uv.user_id = $1
		ORDER BY uv.created_at DESC
		LIMIT $2 OFFSET $3
	`

	rows, err := s.pool.Query(ctx, query, userID, limit, offset)
	if err != nil {
		return nil, fmt.Errorf("failed to query user violations: %w", err)
	}
	defer rows.Close()

	var violations []models.UserViolationResponse
	for rows.Next() {
		var violation models.UserViolationResponse
		var postContent, commentContent sql.NullString
		var canAppeal bool

		err := rows.Scan(
			&violation.ID,
			&violation.UserID,
			&violation.ModerationFlagID,
			&violation.ViolationType,
			&violation.ViolationReason,
			&violation.SeverityScore,
			&violation.IsAppealable,
			&violation.AppealDeadline,
			&violation.Status,
			&violation.CreatedAt,
			&violation.UpdatedAt,
			&violation.FlagReason,
			&postContent,
			&commentContent,
			&canAppeal,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan violation: %w", err)
		}

		if postContent.Valid {
			violation.PostContent = postContent.String
		}
		if commentContent.Valid {
			violation.CommentContent = commentContent.String
		}
		violation.CanAppeal = canAppeal

		// Get appeal if exists
		if violation.Status == models.ViolationStatusAppealed {
			appeal, err := s.GetAppealForViolation(ctx, violation.ID)
			if err == nil {
				violation.Appeal = appeal
			}
		}

		violations = append(violations, violation)
	}

	return violations, nil
}

// CreateAppeal creates an appeal for a violation
func (s *AppealService) CreateAppeal(ctx context.Context, userID uuid.UUID, req *models.UserAppealRequest) (*models.UserAppeal, error) {
	// First check if violation exists and is appealable
	var violation models.UserViolation
	checkQuery := `
		SELECT uv.*, ag.appeal_window_hours
		FROM user_violations uv
		LEFT JOIN appeal_guidelines ag ON uv.violation_type = ag.violation_type AND ag.is_active = true
		WHERE uv.id = $1 AND uv.user_id = $2
	`

	var appealWindowHours sql.NullInt32
	err := s.pool.QueryRow(ctx, checkQuery, req.UserViolationID, userID).Scan(
		&violation.ID,
		&violation.UserID,
		&violation.ModerationFlagID,
		&violation.ViolationType,
		&violation.ViolationReason,
		&violation.SeverityScore,
		&violation.IsAppealable,
		&violation.AppealDeadline,
		&violation.Status,
		&violation.CreatedAt,
		&violation.UpdatedAt,
		&appealWindowHours,
	)

	if err != nil {
		return nil, fmt.Errorf("violation not found or not accessible: %w", err)
	}

	// Check if appeal is allowed
	if !violation.IsAppealable {
		return nil, fmt.Errorf("this violation type is not appealable")
	}

	if violation.Status != models.ViolationStatusActive {
		return nil, fmt.Errorf("violation has already been appealed or resolved")
	}

	if violation.AppealDeadline != nil && time.Now().After(*violation.AppealDeadline) {
		return nil, fmt.Errorf("appeal deadline has passed")
	}

	// Check monthly appeal limit
	if appealWindowHours.Valid {
		limitQuery := `
			SELECT COUNT(*) 
			FROM user_appeals ua
			JOIN user_violations uv ON ua.user_violation_id = uv.id
			WHERE ua.user_id = $1 
			AND ua.created_at >= NOW() - INTERVAL '1 month'
			AND uv.violation_type = $2
		`
		var appealCount int
		err = s.pool.QueryRow(ctx, limitQuery, userID, violation.ViolationType).Scan(&appealCount)
		if err != nil {
			return nil, fmt.Errorf("failed to check appeal limit: %w", err)
		}

		// Get max appeals per month from guidelines
		var maxAppeals int
		guidelineQuery := `SELECT max_appeals_per_month FROM appeal_guidelines WHERE violation_type = $1 AND is_active = true`
		err = s.pool.QueryRow(ctx, guidelineQuery, violation.ViolationType).Scan(&maxAppeals)
		if err != nil {
			return nil, fmt.Errorf("failed to get appeal guidelines: %w", err)
		}

		if appealCount >= maxAppeals {
			return nil, fmt.Errorf("monthly appeal limit exceeded")
		}
	}

	// Create the appeal
	evidenceJSON, _ := json.Marshal(req.EvidenceURLs)
	query := `
		INSERT INTO user_appeals (user_violation_id, user_id, appeal_reason, appeal_context, evidence_urls)
		VALUES ($1, $2, $3, $4, $5)
		RETURNING id, created_at, updated_at
	`

	var appeal models.UserAppeal
	err = s.pool.QueryRow(ctx, query, req.UserViolationID, userID, req.AppealReason, req.AppealContext, evidenceJSON).Scan(
		&appeal.ID,
		&appeal.CreatedAt,
		&appeal.UpdatedAt,
	)

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

	// Update violation status
	updateQuery := `UPDATE user_violations SET status = 'appealed', updated_at = NOW() WHERE id = $1`
	_, err = s.pool.Exec(ctx, updateQuery, req.UserViolationID)
	if err != nil {
		return nil, fmt.Errorf("failed to update violation status: %w", err)
	}

	// Populate appeal details
	appeal.UserViolationID = req.UserViolationID
	appeal.UserID = userID
	appeal.AppealReason = req.AppealReason
	appeal.AppealContext = req.AppealContext
	appeal.EvidenceURLs = req.EvidenceURLs
	appeal.Status = models.AppealStatusPending

	return &appeal, nil
}

// GetAppealForViolation returns the appeal for a specific violation
func (s *AppealService) GetAppealForViolation(ctx context.Context, violationID uuid.UUID) (*models.UserAppeal, error) {
	query := `
		SELECT id, user_violation_id, user_id, appeal_reason, appeal_context, evidence_urls, 
			   status, reviewed_by, review_decision, reviewed_at, created_at, updated_at
		FROM user_appeals 
		WHERE user_violation_id = $1
		ORDER BY created_at DESC
		LIMIT 1
	`

	var appeal models.UserAppeal
	var evidenceJSON []byte

	err := s.pool.QueryRow(ctx, query, violationID).Scan(
		&appeal.ID,
		&appeal.UserViolationID,
		&appeal.UserID,
		&appeal.AppealReason,
		&appeal.AppealContext,
		&evidenceJSON,
		&appeal.Status,
		&appeal.ReviewedBy,
		&appeal.ReviewDecision,
		&appeal.ReviewedAt,
		&appeal.CreatedAt,
		&appeal.UpdatedAt,
	)

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

	if len(evidenceJSON) > 0 {
		json.Unmarshal(evidenceJSON, &appeal.EvidenceURLs)
	}

	return &appeal, nil
}

// GetUserViolationSummary returns a summary of user's violation history
func (s *AppealService) GetUserViolationSummary(ctx context.Context, userID uuid.UUID) (*models.UserViolationSummary, error) {
	query := `
		SELECT 
			COUNT(*) as total_violations,
			COUNT(CASE WHEN violation_type = 'hard_violation' THEN 1 END) as hard_violations,
			COUNT(CASE WHEN violation_type = 'soft_violation' THEN 1 END) as soft_violations,
			COUNT(CASE WHEN status = 'appealed' THEN 1 END) as active_appeals,
			u.current_status,
			u.ban_expiry
		FROM user_violations uv
		LEFT JOIN user_violation_history u ON uv.user_id = u.user_id AND u.violation_date = CURRENT_DATE
		WHERE uv.user_id = $1
	`

	var summary models.UserViolationSummary
	var banExpiry sql.NullTime

	err := s.pool.QueryRow(ctx, query, userID).Scan(
		&summary.TotalViolations,
		&summary.HardViolations,
		&summary.SoftViolations,
		&summary.ActiveAppeals,
		&summary.CurrentStatus,
		&banExpiry,
	)

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

	if banExpiry.Valid {
		summary.BanExpiry = &banExpiry.Time
	}

	// Get recent violations
	recentViolations, err := s.GetUserViolations(ctx, userID, 5, 0)
	if err != nil {
		return nil, fmt.Errorf("failed to get recent violations: %w", err)
	}
	summary.RecentViolations = recentViolations

	return &summary, nil
}

// ReviewAppeal allows an admin to review and decide on an appeal
func (s *AppealService) ReviewAppeal(ctx context.Context, appealID uuid.UUID, adminID uuid.UUID, decision string, reviewDecision string) error {
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(ctx)

	// Update appeal
	appealQuery := `
		UPDATE user_appeals 
		SET status = CASE WHEN $1 = 'approved' THEN 'approved' ELSE 'rejected' END,
			reviewed_by = $2,
			review_decision = $3,
			reviewed_at = NOW(),
			updated_at = NOW()
		WHERE id = $4
	`

	_, err = tx.Exec(ctx, appealQuery, decision, adminID, reviewDecision, appealID)
	if err != nil {
		return fmt.Errorf("failed to update appeal: %w", err)
	}

	// Get violation ID for this appeal
	var violationID uuid.UUID
	violationQuery := `SELECT user_violation_id FROM user_appeals WHERE id = $1`
	err = tx.QueryRow(ctx, violationQuery, appealID).Scan(&violationID)
	if err != nil {
		return fmt.Errorf("failed to get violation ID: %w", err)
	}

	// Update violation status
	violationStatus := "upheld"
	if decision == "approved" {
		violationStatus = "overturned"
	}

	violationUpdateQuery := `
		UPDATE user_violations 
		SET status = $1, updated_at = NOW()
		WHERE id = $2
	`

	_, err = tx.Exec(ctx, violationUpdateQuery, violationStatus, violationID)
	if err != nil {
		return fmt.Errorf("failed to update violation status: %w", err)
	}

	// Update violation history
	historyQuery := `
		UPDATE user_violation_history 
		SET appeals_filed = appeals_filed + 1,
			appeals_upheld = appeals_upheld + CASE WHEN $1 = 'rejected' THEN 1 ELSE 0 END,
			appeals_overturned = appeals_overturned + CASE WHEN $1 = 'approved' THEN 1 ELSE 0 END,
			updated_at = NOW()
		WHERE user_id = (SELECT user_id FROM user_violations WHERE id = $2) 
		AND violation_date = CURRENT_DATE
	`

	_, err = tx.Exec(ctx, historyQuery, decision, violationID)
	if err != nil {
		return fmt.Errorf("failed to update violation history: %w", err)
	}

	return tx.Commit(ctx)
}
