package service

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"log/slog"
	"net/http"
	"strings"
	"time"

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

type Indexer struct {
	ID             int64           `json:"id"`
	Name           string          `json:"name"`
	Implementation string          `json:"implementation"`
	URL            string          `json:"url"`
	APIKey         *string         `json:"-"`
	Categories     json.RawMessage `json:"categories"`
	Settings       json.RawMessage `json:"settings"`
	Enabled        bool            `json:"enabled"`
	Priority       int             `json:"priority"`
	LastSuccessAt  *time.Time      `json:"last_success_at,omitempty"`
	FailureCount   int             `json:"failure_count"`
	DisabledUntil  *time.Time      `json:"disabled_until,omitempty"`
	CreatedAt      time.Time       `json:"created_at"`
	UpdatedAt      time.Time       `json:"updated_at"`
}

type IndexerResponse struct {
	ID             int64           `json:"id"`
	Name           string          `json:"name"`
	Implementation string          `json:"implementation"`
	URL            string          `json:"url"`
	Categories     json.RawMessage `json:"categories"`
	Settings       json.RawMessage `json:"settings"`
	Enabled        bool            `json:"enabled"`
	Priority       int             `json:"priority"`
	LastSuccessAt  *time.Time      `json:"last_success_at,omitempty"`
	FailureCount   int             `json:"failure_count"`
	DisabledUntil  *time.Time      `json:"disabled_until,omitempty"`
	CreatedAt      time.Time       `json:"created_at"`
	UpdatedAt      time.Time       `json:"updated_at"`
}

type IndexerTestResult struct {
	Success    bool   `json:"success"`
	Error      string `json:"error,omitempty"`
	StatusCode int    `json:"status_code,omitempty"`
}

type IndexerStats struct {
	ID           int64   `json:"id"`
	Name         string  `json:"name"`
	TotalGrabs   int     `json:"total_grabs"`
	TotalFailed  int     `json:"total_failed"`
	SuccessRate  float64 `json:"success_rate"`
	FailureCount int     `json:"failure_count"`
	LastSuccess  string  `json:"last_success,omitempty"`
}

type CreateIndexerRequest struct {
	Name           string          `json:"name"`
	Implementation string          `json:"implementation"`
	URL            string          `json:"url"`
	APIKey         *string         `json:"api_key,omitempty"`
	Categories     json.RawMessage `json:"categories,omitempty"`
	Settings       json.RawMessage `json:"settings,omitempty"`
	Enabled        *bool           `json:"enabled,omitempty"`
	Priority       *int            `json:"priority,omitempty"`
}

type UpdateIndexerRequest struct {
	Name           *string         `json:"name,omitempty"`
	Implementation *string         `json:"implementation,omitempty"`
	URL            *string         `json:"url,omitempty"`
	APIKey         *string         `json:"api_key,omitempty"`
	Categories     json.RawMessage `json:"categories,omitempty"`
	Settings       json.RawMessage `json:"settings,omitempty"`
	Enabled        *bool           `json:"enabled,omitempty"`
	Priority       *int            `json:"priority,omitempty"`
}

const indexerColumns = `id, name, implementation, url, api_key, categories, settings,
	enabled, priority, last_success_at, failure_count, disabled_until, created_at, updated_at`

type IndexerService struct {
	db             *db.DB
	cardigannEngine *cardigann.CardigannEngine
}

func NewIndexerService(database *db.DB) *IndexerService {
	return &IndexerService{db: database}
}

// SetCardigannEngine sets the Cardigann engine for advanced indexer testing.
func (s *IndexerService) SetCardigannEngine(engine *cardigann.CardigannEngine) {
	s.cardigannEngine = engine
}

// CardigannIndexerConfig holds the Cardigann-specific configuration stored in settings JSONB.
type CardigannIndexerConfig struct {
	YAML   string            `json:"yaml"`
	Config map[string]string `json:"config"`
}

// GetCardigannConfig extracts Cardigann configuration from indexer settings JSONB.
func (s *IndexerService) GetCardigannConfig(settings json.RawMessage) (*CardigannIndexerConfig, error) {
	if len(settings) == 0 {
		return nil, fmt.Errorf("no settings provided")
	}
	var cfg CardigannIndexerConfig
	if err := json.Unmarshal(settings, &cfg); err != nil {
		return nil, fmt.Errorf("parse cardigann config: %w", err)
	}
	if cfg.YAML == "" {
		return nil, fmt.Errorf("cardigann settings missing yaml field")
	}
	return &cfg, nil
}

func scanIndexer(scanner interface{ Scan(...interface{}) error }) (*Indexer, error) {
	var idx Indexer
	var apiKey sql.NullString
	var categories, settings []byte
	var lastSuccessAt, disabledUntil sql.NullTime

	err := scanner.Scan(&idx.ID, &idx.Name, &idx.Implementation, &idx.URL, &apiKey,
		&categories, &settings, &idx.Enabled, &idx.Priority,
		&lastSuccessAt, &idx.FailureCount, &disabledUntil,
		&idx.CreatedAt, &idx.UpdatedAt)
	if err != nil {
		return nil, err
	}

	if apiKey.Valid {
		idx.APIKey = &apiKey.String
	}
	idx.Categories = json.RawMessage(categories)
	idx.Settings = json.RawMessage(settings)
	if lastSuccessAt.Valid {
		idx.LastSuccessAt = &lastSuccessAt.Time
	}
	if disabledUntil.Valid {
		idx.DisabledUntil = &disabledUntil.Time
	}
	return &idx, nil
}

func indexerToResponse(idx *Indexer) IndexerResponse {
	return IndexerResponse{
		ID:             idx.ID,
		Name:           idx.Name,
		Implementation: idx.Implementation,
		URL:            idx.URL,
		Categories:     idx.Categories,
		Settings:       idx.Settings,
		Enabled:        idx.Enabled,
		Priority:       idx.Priority,
		LastSuccessAt:  idx.LastSuccessAt,
		FailureCount:   idx.FailureCount,
		DisabledUntil:  idx.DisabledUntil,
		CreatedAt:      idx.CreatedAt,
		UpdatedAt:      idx.UpdatedAt,
	}
}

func (s *IndexerService) List(ctx context.Context) ([]IndexerResponse, error) {
	rows, err := s.db.Pool.Query(ctx,
		fmt.Sprintf("SELECT %s FROM indexers ORDER BY priority, name", indexerColumns))
	if err != nil {
		return nil, fmt.Errorf("list indexers: %w", err)
	}
	defer rows.Close()

	var items []IndexerResponse
	for rows.Next() {
		idx, err := scanIndexer(rows)
		if err != nil {
			slog.Error("failed to scan indexer", "error", err)
			continue
		}
		items = append(items, indexerToResponse(idx))
	}

	return items, nil
}

func (s *IndexerService) Create(ctx context.Context, req CreateIndexerRequest) (int64, error) {
	categories := req.Categories
	if categories == nil {
		categories = json.RawMessage("[]")
	}
	settings := req.Settings
	if settings == nil {
		settings = json.RawMessage("{}")
	}
	enabled := true
	if req.Enabled != nil {
		enabled = *req.Enabled
	}
	priority := 0
	if req.Priority != nil {
		priority = *req.Priority
	}

	// For Cardigann indexers, extract URL from YAML definition
	url := req.URL
	if req.Implementation == "cardigann" {
		cfg, err := s.GetCardigannConfig(settings)
		if err != nil {
			return 0, fmt.Errorf("invalid cardigann settings: %w", err)
		}
		def, err := cardigann.ParseDefinition([]byte(cfg.YAML))
		if err != nil {
			return 0, fmt.Errorf("invalid cardigann YAML: %w", err)
		}
		if len(def.Links) > 0 {
			url = def.Links[0]
		}
		if req.Name == "" {
			req.Name = def.Name
		}
	}

	var id int64
	err := s.db.Pool.QueryRow(ctx,
		`INSERT INTO indexers (name, implementation, url, api_key, categories, settings, enabled, priority)
		 VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`,
		req.Name, req.Implementation, url, req.APIKey, categories, settings, enabled, priority).Scan(&id)
	if err != nil {
		return 0, fmt.Errorf("create indexer: %w", err)
	}

	return id, nil
}

func (s *IndexerService) Update(ctx context.Context, id int64, req UpdateIndexerRequest) error {
	var setClauses []string
	var args []interface{}
	idx := 1

	addCol := func(col string, val interface{}) {
		setClauses = append(setClauses, fmt.Sprintf("%s = $%d", col, idx))
		args = append(args, val)
		idx++
	}

	if req.Name != nil {
		addCol("name", *req.Name)
	}
	if req.Implementation != nil {
		addCol("implementation", *req.Implementation)
	}
	if req.URL != nil {
		addCol("url", *req.URL)
	}
	if req.APIKey != nil {
		addCol("api_key", *req.APIKey)
	}
	if req.Categories != nil {
		addCol("categories", req.Categories)
	}
	if req.Settings != nil {
		addCol("settings", req.Settings)
	}
	if req.Enabled != nil {
		addCol("enabled", *req.Enabled)
	}
	if req.Priority != nil {
		addCol("priority", *req.Priority)
	}

	if len(setClauses) == 0 {
		return fmt.Errorf("no fields to update")
	}

	addCol("updated_at", time.Now())

	query := fmt.Sprintf("UPDATE indexers SET %s WHERE id = $%d",
		strings.Join(setClauses, ", "), idx)
	args = append(args, id)

	tag, err := s.db.Pool.Exec(ctx, query, args...)
	if err != nil {
		return fmt.Errorf("update indexer: %w", err)
	}
	if tag.RowsAffected() == 0 {
		return fmt.Errorf("indexer not found")
	}

	return nil
}

func (s *IndexerService) Delete(ctx context.Context, id int64) error {
	tag, err := s.db.Pool.Exec(ctx, "DELETE FROM indexers WHERE id = $1", id)
	if err != nil {
		return fmt.Errorf("delete indexer: %w", err)
	}
	if tag.RowsAffected() == 0 {
		return fmt.Errorf("indexer not found")
	}
	return nil
}

func (s *IndexerService) Test(ctx context.Context, id int64) (*IndexerTestResult, error) {
	row := s.db.Pool.QueryRow(ctx,
		fmt.Sprintf("SELECT %s FROM indexers WHERE id = $1", indexerColumns), id)

	idx, err := scanIndexer(row)
	if err != nil {
		return nil, fmt.Errorf("indexer not found")
	}

	// Cardigann indexers: parse YAML, perform connectivity check
	if idx.Implementation == "cardigann" {
		return s.testCardigannIndexer(ctx, idx)
	}

	testURL := idx.URL
	switch idx.Implementation {
	case "newznab", "torznab":
		testURL = testURL + "/api?t=caps"
		if idx.APIKey != nil && *idx.APIKey != "" {
			testURL = testURL + "&apikey=" + *idx.APIKey
		}
	default:
		testURL = strings.TrimRight(testURL, "/")
	}

	client := &http.Client{Timeout: 10 * time.Second}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, testURL, nil)
	if err != nil {
		return &IndexerTestResult{Success: false, Error: err.Error()}, nil
	}

	resp, err := client.Do(req)
	if err != nil {
		s.db.Pool.Exec(ctx,
			"UPDATE indexers SET failure_count = failure_count + 1, updated_at = NOW() WHERE id = $1", id)
		return &IndexerTestResult{Success: false, Error: err.Error()}, nil
	}
	resp.Body.Close()

	if resp.StatusCode >= 400 {
		s.db.Pool.Exec(ctx,
			"UPDATE indexers SET failure_count = failure_count + 1, updated_at = NOW() WHERE id = $1", id)
		return &IndexerTestResult{
			Success:    false,
			Error:      fmt.Sprintf("HTTP %d", resp.StatusCode),
			StatusCode: resp.StatusCode,
		}, nil
	}

	s.db.Pool.Exec(ctx,
		"UPDATE indexers SET last_success_at = NOW(), failure_count = 0, updated_at = NOW() WHERE id = $1", id)

	return &IndexerTestResult{
		Success:    true,
		StatusCode: resp.StatusCode,
	}, nil
}

func (s *IndexerService) testCardigannIndexer(ctx context.Context, idx *Indexer) (*IndexerTestResult, error) {
	cfg, err := s.GetCardigannConfig(idx.Settings)
	if err != nil {
		return &IndexerTestResult{Success: false, Error: fmt.Sprintf("invalid cardigann config: %v", err)}, nil
	}

	def, err := cardigann.ParseDefinition([]byte(cfg.YAML))
	if err != nil {
		return &IndexerTestResult{Success: false, Error: fmt.Sprintf("invalid YAML: %v", err)}, nil
	}

	// Use CardigannEngine for full test if available
	if s.cardigannEngine != nil {
		result, err := s.cardigannEngine.Test(ctx, def, cfg.Config)
		if err != nil {
			return &IndexerTestResult{Success: false, Error: err.Error()}, nil
		}

		if !result.Success {
			s.db.Pool.Exec(ctx,
				"UPDATE indexers SET failure_count = failure_count + 1, updated_at = NOW() WHERE id = $1", idx.ID)
		} else {
			s.db.Pool.Exec(ctx,
				"UPDATE indexers SET last_success_at = NOW(), failure_count = 0, updated_at = NOW() WHERE id = $1", idx.ID)
		}

		return &IndexerTestResult{
			Success: result.Success,
			Error:   result.Error,
		}, nil
	}

	// Fallback: basic connectivity check to first link
	if len(def.Links) == 0 {
		return &IndexerTestResult{Success: false, Error: "definition has no links"}, nil
	}

	testURL := def.Links[0]
	client := &http.Client{Timeout: 10 * time.Second}
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, testURL, nil)
	if err != nil {
		return &IndexerTestResult{Success: false, Error: err.Error()}, nil
	}

	resp, err := client.Do(req)
	if err != nil {
		s.db.Pool.Exec(ctx,
			"UPDATE indexers SET failure_count = failure_count + 1, updated_at = NOW() WHERE id = $1", idx.ID)
		return &IndexerTestResult{Success: false, Error: err.Error()}, nil
	}
	resp.Body.Close()

	if resp.StatusCode >= 400 {
		s.db.Pool.Exec(ctx,
			"UPDATE indexers SET failure_count = failure_count + 1, updated_at = NOW() WHERE id = $1", idx.ID)
		return &IndexerTestResult{
			Success:    false,
			Error:      fmt.Sprintf("HTTP %d", resp.StatusCode),
			StatusCode: resp.StatusCode,
		}, nil
	}

	s.db.Pool.Exec(ctx,
		"UPDATE indexers SET last_success_at = NOW(), failure_count = 0, updated_at = NOW() WHERE id = $1", idx.ID)

	return &IndexerTestResult{
		Success:    true,
		StatusCode: resp.StatusCode,
	}, nil
}

func (s *IndexerService) ListEnabled(ctx context.Context) ([]Indexer, error) {
	rows, err := s.db.Pool.Query(ctx,
		fmt.Sprintf("SELECT %s FROM indexers WHERE enabled = true AND (disabled_until IS NULL OR disabled_until < NOW()) ORDER BY priority, name", indexerColumns))
	if err != nil {
		return nil, fmt.Errorf("list enabled indexers: %w", err)
	}
	defer rows.Close()

	var items []Indexer
	for rows.Next() {
		idx, err := scanIndexer(rows)
		if err != nil {
			slog.Error("failed to scan indexer", "error", err)
			continue
		}
		items = append(items, *idx)
	}

	return items, nil
}

func (s *IndexerService) RecordSuccess(ctx context.Context, id int64) error {
	_, err := s.db.Pool.Exec(ctx,
		"UPDATE indexers SET failure_count = 0, last_success_at = NOW(), disabled_until = NULL, updated_at = NOW() WHERE id = $1", id)
	if err != nil {
		return fmt.Errorf("record indexer success: %w", err)
	}
	return nil
}

func (s *IndexerService) RecordFailure(ctx context.Context, id int64) error {
	var failureCount int
	err := s.db.Pool.QueryRow(ctx,
		"SELECT failure_count FROM indexers WHERE id = $1", id).Scan(&failureCount)
	if err != nil {
		return fmt.Errorf("get indexer failure count: %w", err)
	}

	failureCount++

	if failureCount >= 5 {
		backoffMinutes := 1 << min(failureCount, 6)
		if backoffMinutes > 60 {
			backoffMinutes = 60
		}
		disabledUntil := time.Now().Add(time.Duration(backoffMinutes) * time.Minute)
		_, err = s.db.Pool.Exec(ctx,
			"UPDATE indexers SET failure_count = $1, disabled_until = $2, updated_at = NOW() WHERE id = $3",
			failureCount, disabledUntil, id)
		slog.Warn("indexer auto-disabled after consecutive failures", "id", id, "failure_count", failureCount, "disabled_until", disabledUntil)
	} else {
		_, err = s.db.Pool.Exec(ctx,
			"UPDATE indexers SET failure_count = $1, updated_at = NOW() WHERE id = $2",
			failureCount, id)
		slog.Warn("indexer search failed", "id", id, "failure_count", failureCount)
	}

	if err != nil {
		return fmt.Errorf("record indexer failure: %w", err)
	}
	return nil
}

func MediaTypeToCategory(mediaType string) string {
	switch strings.ToLower(mediaType) {
	case "movie":
		return "2000"
	case "series", "episode":
		return "5000"
	case "music", "album":
		return "3000"
	case "book":
		return "7000"
	case "audiobook":
		return "3030"
	default:
		return ""
	}
}

func (s *IndexerService) Stats(ctx context.Context, id int64) (*IndexerStats, error) {
	var name string
	var failureCount int
	var lastSuccessAt sql.NullTime

	err := s.db.Pool.QueryRow(ctx,
		"SELECT name, failure_count, last_success_at FROM indexers WHERE id = $1", id,
	).Scan(&name, &failureCount, &lastSuccessAt)
	if err != nil {
		return nil, fmt.Errorf("indexer not found")
	}

	var totalGrabs, totalFailed int
	s.db.Pool.QueryRow(ctx,
		`SELECT COUNT(*) FILTER (WHERE action IN ('grabbed', 'imported')),
		        COUNT(*) FILTER (WHERE action = 'failed')
		 FROM download_history WHERE indexer = $1`, name,
	).Scan(&totalGrabs, &totalFailed)

	successRate := 0.0
	total := totalGrabs + totalFailed
	if total > 0 {
		successRate = float64(totalGrabs) / float64(total) * 100
	}

	result := &IndexerStats{
		ID:           id,
		Name:         name,
		TotalGrabs:   totalGrabs,
		TotalFailed:  totalFailed,
		SuccessRate:  successRate,
		FailureCount: failureCount,
	}
	if lastSuccessAt.Valid {
		result.LastSuccess = lastSuccessAt.Time.Format(time.RFC3339)
	}

	return result, nil
}
