package service

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"
	"time"

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

type QualityTier struct {
	Name        string `json:"name"`
	Rank        int    `json:"rank"`
	Resolution  string `json:"resolution"`
	Source      string `json:"source"`
	Codec       string `json:"codec"`
	MinLinesize int    `json:"min_linesize"`
}

var QualityTiers = []QualityTier{
	{Name: "SDTV", Rank: 1, Resolution: "", Source: "television"},
	{Name: "SDDVD", Rank: 2, Resolution: "480p", Source: "dvd"},
	{Name: "WEBDL-480p", Rank: 3, Resolution: "480p", Source: "web"},
	{Name: "HDTV-720p", Rank: 4, Resolution: "720p", Source: "television"},
	{Name: "WEBDL-720p", Rank: 5, Resolution: "720p", Source: "web"},
	{Name: "Bluray-720p", Rank: 6, Resolution: "720p", Source: "bluray"},
	{Name: "HDTV-1080p", Rank: 7, Resolution: "1080p", Source: "television"},
	{Name: "WEBDL-1080p", Rank: 8, Resolution: "1080p", Source: "web"},
	{Name: "Bluray-1080p", Rank: 9, Resolution: "1080p", Source: "bluray"},
	{Name: "Remux-1080p", Rank: 10, Resolution: "1080p", Source: "remux"},
	{Name: "HDTV-2160p", Rank: 11, Resolution: "2160p", Source: "television"},
	{Name: "WEBDL-2160p", Rank: 12, Resolution: "2160p", Source: "web"},
	{Name: "Bluray-2160p", Rank: 13, Resolution: "2160p", Source: "bluray"},
	{Name: "Remux-2160p", Rank: 14, Resolution: "2160p", Source: "remux"},
}

var sourceMatchMap = map[string][]string{
	"television": {"HDTV", "PDTV", "SDTV"},
	"web":        {"WEB-DL", "WEBDL", "WEBRip", "WEB"},
	"bluray":     {"BluRay", "BDRip", "BRRip"},
	"remux":      {"REMUX", "Remux"},
	"dvd":        {"DVDRip", "DVD"},
}

func SourceMatch(tierSource, releaseSource string) bool {
	matches, ok := sourceMatchMap[tierSource]
	if !ok {
		return strings.EqualFold(tierSource, releaseSource)
	}
	for _, m := range matches {
		if strings.EqualFold(m, releaseSource) {
			return true
		}
	}
	return false
}

func GetTierByName(name string) *QualityTier {
	for i := range QualityTiers {
		if QualityTiers[i].Name == name {
			return &QualityTiers[i]
		}
	}
	return nil
}

func GetTiers() []QualityTier {
	result := make([]QualityTier, len(QualityTiers))
	copy(result, QualityTiers)
	return result
}

func GetTiersByMediaType(mediaType string) []QualityTier {
	return GetTiers()
}

type QualityProfile struct {
	ID                int64    `json:"id"`
	Name              string   `json:"name"`
	MediaTypes        []string `json:"media_types"`
	CutoffQuality     string   `json:"cutoff_quality"`
	AllowedQualities  []string `json:"allowed_qualities"`
	CreatedAt         string   `json:"created_at"`
	UpdatedAt         string   `json:"updated_at"`
}

type QualityService struct {
	db *db.DB
}

func NewQualityService(database *db.DB) *QualityService {
	return &QualityService{db: database}
}

const qualityProfileColumns = `id, name, media_types, cutoff_quality, allowed_qualities, created_at, updated_at`

func scanQualityProfile(scanner interface{ Scan(...interface{}) error }) (*QualityProfile, error) {
	var p QualityProfile
	var mediaTypes []string
	var cutoffQuality []byte
	var allowedQualities []byte
	var createdAt, updatedAt time.Time

	err := scanner.Scan(&p.ID, &p.Name, &mediaTypes, &cutoffQuality, &allowedQualities, &createdAt, &updatedAt)
	if err != nil {
		return nil, err
	}

	p.MediaTypes = mediaTypes

	if err := json.Unmarshal(cutoffQuality, &p.CutoffQuality); err != nil {
		p.CutoffQuality = ""
	}

	if err := json.Unmarshal(allowedQualities, &p.AllowedQualities); err != nil {
		p.AllowedQualities = []string{}
	}

	p.CreatedAt = createdAt.Format(time.RFC3339)
	p.UpdatedAt = updatedAt.Format(time.RFC3339)

	return &p, nil
}

func (s *QualityService) List(ctx context.Context) ([]QualityProfile, error) {
	rows, err := s.db.Pool.Query(ctx,
		fmt.Sprintf("SELECT %s FROM quality_profiles ORDER BY name", qualityProfileColumns))
	if err != nil {
		return nil, fmt.Errorf("list quality profiles: %w", err)
	}
	defer rows.Close()

	var items []QualityProfile
	for rows.Next() {
		p, err := scanQualityProfile(rows)
		if err != nil {
			continue
		}
		items = append(items, *p)
	}

	return items, nil
}

func (s *QualityService) Create(ctx context.Context, name string, mediaTypes []string, cutoffQuality string, allowedQualities []string) (int64, error) {
	if GetTierByName(cutoffQuality) == nil {
		return 0, fmt.Errorf("invalid cutoff quality tier: %s", cutoffQuality)
	}

	for _, q := range allowedQualities {
		if GetTierByName(q) == nil {
			return 0, fmt.Errorf("invalid allowed quality tier: %s", q)
		}
	}

	cutoffJSON, _ := json.Marshal(cutoffQuality)
	allowedJSON, _ := json.Marshal(allowedQualities)

	var id int64
	err := s.db.Pool.QueryRow(ctx,
		`INSERT INTO quality_profiles (name, media_types, cutoff_quality, allowed_qualities)
		 VALUES ($1, $2::media_type[], $3, $4) RETURNING id`,
		name, mediaTypes, cutoffJSON, allowedJSON).Scan(&id)
	if err != nil {
		return 0, fmt.Errorf("create quality profile: %w", err)
	}

	return id, nil
}

type UpdateQualityProfileRequest struct {
	Name              *string  `json:"name,omitempty"`
	MediaTypes        []string `json:"media_types,omitempty"`
	CutoffQuality     *string  `json:"cutoff_quality,omitempty"`
	AllowedQualities  []string `json:"allowed_qualities,omitempty"`
}

func (s *QualityService) Update(ctx context.Context, id int64, req UpdateQualityProfileRequest) 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.MediaTypes != nil {
		setClauses = append(setClauses, fmt.Sprintf("media_types = $%d::media_type[]", idx))
		args = append(args, req.MediaTypes)
		idx++
	}
	if req.CutoffQuality != nil {
		if GetTierByName(*req.CutoffQuality) == nil {
			return fmt.Errorf("invalid cutoff quality tier: %s", *req.CutoffQuality)
		}
		cutoffJSON, _ := json.Marshal(*req.CutoffQuality)
		addCol("cutoff_quality", cutoffJSON)
	}
	if req.AllowedQualities != nil {
		for _, q := range req.AllowedQualities {
			if GetTierByName(q) == nil {
				return fmt.Errorf("invalid allowed quality tier: %s", q)
			}
		}
		allowedJSON, _ := json.Marshal(req.AllowedQualities)
		addCol("allowed_qualities", allowedJSON)
	}

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

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

	query := fmt.Sprintf("UPDATE quality_profiles 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 quality profile: %w", err)
	}
	if tag.RowsAffected() == 0 {
		return fmt.Errorf("quality profile not found")
	}

	return nil
}

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

func (s *QualityService) GetByID(ctx context.Context, id int64) (*QualityProfile, error) {
	row := s.db.Pool.QueryRow(ctx,
		fmt.Sprintf("SELECT %s FROM quality_profiles WHERE id = $1", qualityProfileColumns), id)
	p, err := scanQualityProfile(row)
	if err != nil {
		return nil, fmt.Errorf("quality profile not found")
	}
	return p, nil
}

func (s *QualityService) NeedsUpgrade(currentQuality string, cutoffQuality string) bool {
	current := GetTierByName(currentQuality)
	cutoff := GetTierByName(cutoffQuality)
	if current == nil || cutoff == nil {
		return false
	}
	return current.Rank < cutoff.Rank
}

func (s *QualityService) IsCutoffMet(currentQuality string, cutoffQuality string) bool {
	current := GetTierByName(currentQuality)
	cutoff := GetTierByName(cutoffQuality)
	if current == nil || cutoff == nil {
		return false
	}
	return current.Rank >= cutoff.Rank
}

func (s *QualityService) GetAllowedTierNames(allowedQualitiesJSON json.RawMessage) []string {
	var names []string
	if err := json.Unmarshal(allowedQualitiesJSON, &names); err != nil {
		return []string{}
	}
	return names
}
