package service

import (
	"context"
	"fmt"
	"log/slog"
	"time"

	"github.com/TopherMayor/unified-media-manager/internal/db"
)

type Request struct {
	ID               int64      `json:"id"`
	MediaID          int64      `json:"media_id"`
	MediaType        string     `json:"media_type"`
	Title            string     `json:"title"`
	RequestedBy      int64      `json:"requested_by"`
	Status           string     `json:"status"`
	QualityProfileID *int64     `json:"quality_profile_id,omitempty"`
	RootFolderID     *int64     `json:"root_folder_id,omitempty"`
	Notes            string     `json:"notes,omitempty"`
	ReviewedBy       *int64     `json:"reviewed_by,omitempty"`
	ReviewedAt       *time.Time `json:"reviewed_at,omitempty"`
	CreatedAt        time.Time  `json:"created_at"`
	UpdatedAt        time.Time  `json:"updated_at"`
}

type CreateRequest struct {
	MediaID          int64  `json:"media_id"`
	MediaType        string `json:"media_type"`
	Title            string `json:"title"`
	QualityProfileID *int64 `json:"quality_profile_id,omitempty"`
	RootFolderID     *int64 `json:"root_folder_id,omitempty"`
	Notes            string `json:"notes,omitempty"`
}

type RequestFilters struct {
	Status      string `json:"status,omitempty"`
	RequestedBy *int64 `json:"requested_by,omitempty"`
	Page        int    `json:"page"`
	PageSize    int    `json:"page_size"`
}

type RequestStats struct {
	Total      int `json:"total"`
	Pending    int `json:"pending"`
	Approved   int `json:"approved"`
	Rejected   int `json:"rejected"`
	Fulfilled  int `json:"fulfilled"`
	Withdrawn  int `json:"withdrawn"`
}

const requestColumns = `id, media_id, media_type, title, requested_by, status, quality_profile_id, root_folder_id, notes, reviewed_by, reviewed_at, created_at, updated_at`

type RequestService struct {
	db        *db.DB
	searchSvc *SearchService
}

func NewRequestService(database *db.DB, searchSvc *SearchService) *RequestService {
	return &RequestService{db: database, searchSvc: searchSvc}
}

func scanRequest(scanner interface{ Scan(...interface{}) error }) (*Request, error) {
	var r Request
	err := scanner.Scan(&r.ID, &r.MediaID, &r.MediaType, &r.Title, &r.RequestedBy, &r.Status,
		&r.QualityProfileID, &r.RootFolderID, &r.Notes,
		&r.ReviewedBy, &r.ReviewedAt, &r.CreatedAt, &r.UpdatedAt)
	if err != nil {
		return nil, err
	}
	return &r, nil
}

func (s *RequestService) Create(ctx context.Context, req CreateRequest, userRole string, userID int64) (int64, error) {
	status := "pending"
	if userRole == "admin" || userRole == "power_user" {
		status = "approved"
	}

	now := time.Now()
	var id int64
	err := s.db.Pool.QueryRow(ctx,
		`INSERT INTO requests (media_id, media_type, title, requested_by, status, quality_profile_id, root_folder_id, notes, created_at, updated_at)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
		 RETURNING id`,
		req.MediaID, req.MediaType, req.Title, userID, status,
		req.QualityProfileID, req.RootFolderID, req.Notes, now, now).Scan(&id)
	if err != nil {
		return 0, fmt.Errorf("create request: %w", err)
	}

	// Auto-approve: trigger search immediately in background
	if status == "approved" && s.searchSvc != nil {
		go func() {
			bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
			defer cancel()
			s.triggerSearch(bgCtx, req)
		}()
	}

	return id, nil
}

func (s *RequestService) List(ctx context.Context, filters RequestFilters) ([]Request, int, error) {
	qb := NewQueryBuilder(1)
	qb.AddLiteral("1=1")

	if filters.Status != "" {
		qb.Add("AND status = $%d", filters.Status)
	}
	if filters.RequestedBy != nil {
		qb.Add("AND requested_by = $%d", *filters.RequestedBy)
	}

	countQuery := fmt.Sprintf("SELECT COUNT(*) FROM requests WHERE %s", qb.conditions[0])
	countArgs := qb.Args()
	if len(qb.conditions) > 1 {
		countQuery = fmt.Sprintf("SELECT COUNT(*) FROM requests %s", qb.Where())
		countArgs = qb.Args()
	}

	var total int
	if err := s.db.Pool.QueryRow(ctx, countQuery, countArgs...).Scan(&total); err != nil {
		return nil, 0, fmt.Errorf("count requests: %w", err)
	}

	offset := (filters.Page - 1) * filters.PageSize
	dataQuery := fmt.Sprintf("SELECT %s FROM requests %s ORDER BY created_at DESC LIMIT $%d OFFSET $%d",
		requestColumns, qb.Where(), qb.Idx(), qb.Idx()+1)
	dataArgs := append(qb.Args(), filters.PageSize, offset)

	rows, err := s.db.Pool.Query(ctx, dataQuery, dataArgs...)
	if err != nil {
		return nil, 0, fmt.Errorf("list requests: %w", err)
	}
	defer rows.Close()

	var items []Request
	for rows.Next() {
		r, err := scanRequest(rows)
		if err != nil {
			slog.Error("failed to scan request", "error", err)
			continue
		}
		items = append(items, *r)
	}

	return items, total, nil
}

func (s *RequestService) GetByID(ctx context.Context, id int64) (*Request, error) {
	row := s.db.Pool.QueryRow(ctx,
		fmt.Sprintf("SELECT %s FROM requests WHERE id = $1", requestColumns), id)

	r, err := scanRequest(row)
	if err != nil {
		return nil, fmt.Errorf("request not found")
	}
	return r, nil
}

func (s *RequestService) Approve(ctx context.Context, id int64, reviewerID int64, notes string) error {
	now := time.Now()
	tag, err := s.db.Pool.Exec(ctx,
		`UPDATE requests SET status = 'approved', reviewed_by = $1, reviewed_at = $2, notes = COALESCE(NULLIF($3, ''), notes), updated_at = $4
		 WHERE id = $5 AND status = 'pending'`, reviewerID, now, notes, now, id)
	if err != nil {
		return fmt.Errorf("approve request: %w", err)
	}
	if tag.RowsAffected() == 0 {
		return fmt.Errorf("request not found or not pending")
	}

	// Trigger search in background
	if s.searchSvc != nil {
		req, err := s.GetByID(ctx, id)
		if err == nil {
			go func() {
				bgCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
				defer cancel()
				s.triggerSearch(bgCtx, CreateRequest{
					MediaID:   req.MediaID,
					MediaType: req.MediaType,
					Title:     req.Title,
				})
			}()
		}
	}

	return nil
}

func (s *RequestService) Reject(ctx context.Context, id int64, reviewerID int64, notes string) error {
	now := time.Now()
	tag, err := s.db.Pool.Exec(ctx,
		`UPDATE requests SET status = 'rejected', reviewed_by = $1, reviewed_at = $2, notes = COALESCE(NULLIF($3, ''), notes), updated_at = $4
		 WHERE id = $5 AND status = 'pending'`, reviewerID, now, notes, now, id)
	if err != nil {
		return fmt.Errorf("reject request: %w", err)
	}
	if tag.RowsAffected() == 0 {
		return fmt.Errorf("request not found or not pending")
	}
	return nil
}

func (s *RequestService) Withdraw(ctx context.Context, id int64, userID int64) error {
	now := time.Now()
	tag, err := s.db.Pool.Exec(ctx,
		`UPDATE requests SET status = 'withdrawn', updated_at = $1
		 WHERE id = $2 AND requested_by = $3 AND status IN ('pending', 'approved')`, now, id, userID)
	if err != nil {
		return fmt.Errorf("withdraw request: %w", err)
	}
	if tag.RowsAffected() == 0 {
		return fmt.Errorf("request not found, not owned by user, or not withdrawable")
	}
	return nil
}

func (s *RequestService) MarkFulfilled(ctx context.Context, mediaID int64) error {
	now := time.Now()
	tag, err := s.db.Pool.Exec(ctx,
		`UPDATE requests SET status = 'fulfilled', updated_at = $1
		 WHERE media_id = $2 AND status = 'approved'`, now, mediaID)
	if err != nil {
		return fmt.Errorf("mark fulfilled: %w", err)
	}
	if tag.RowsAffected() > 0 {
		slog.Info("request marked fulfilled", "media_id", mediaID)
	}
	return nil
}

func (s *RequestService) Stats(ctx context.Context) (*RequestStats, error) {
	var stats RequestStats
	err := s.db.Pool.QueryRow(ctx,
		`SELECT
		   COUNT(*) FILTER (WHERE 1=1) AS total,
		   COUNT(*) FILTER (WHERE status = 'pending') AS pending,
		   COUNT(*) FILTER (WHERE status = 'approved') AS approved,
		   COUNT(*) FILTER (WHERE status = 'rejected') AS rejected,
		   COUNT(*) FILTER (WHERE status = 'fulfilled') AS fulfilled,
		   COUNT(*) FILTER (WHERE status = 'withdrawn') AS withdrawn
		 FROM requests`).Scan(&stats.Total, &stats.Pending, &stats.Approved, &stats.Rejected, &stats.Fulfilled, &stats.Withdrawn)
	if err != nil {
		return nil, fmt.Errorf("request stats: %w", err)
	}
	return &stats, nil
}

func (s *RequestService) triggerSearch(ctx context.Context, req CreateRequest) {
	if req.Title == "" {
		return
	}
	_, err := s.searchSvc.Search(ctx, SearchRequest{
		Query:     req.Title,
		MediaType: req.MediaType,
	})
	if err != nil {
		slog.Error("auto-search for request failed", "title", req.Title, "error", err)
	} else {
		slog.Info("auto-search triggered for request", "title", req.Title)
	}
}
