package service

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

type MusicBrainzProvider struct {
	baseURL    string
	userAgent  string
	httpClient *http.Client
	lastRequest time.Time
}

func NewMusicBrainzProvider() *MusicBrainzProvider {
	return &MusicBrainzProvider{
		baseURL:   "https://musicbrainz.org/ws/2",
		userAgent: "UnifiedMediaManager/1.0 (https://github.com/TopherMayor/unified-media-manager)",
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
}

type mbArtistSearchResponse struct {
	Count   int        `json:"count"`
	Artists []mbArtist `json:"artists"`
}

type mbReleaseGroupSearchResponse struct {
	Count         int            `json:"count"`
	ReleaseGroups []mbReleaseGroup `json:"release-groups"`
}

type mbArtist struct {
	ID             string       `json:"id"`
	Name           string       `json:"name"`
	SortName       string       `json:"sort-name"`
	Disambiguation string       `json:"disambiguation"`
	LifeSpan       mbLifeSpan   `json:"life-span"`
}

type mbLifeSpan struct {
	Begin string `json:"begin"`
	End   string `json:"end"`
}

type mbReleaseGroup struct {
	ID             string          `json:"id"`
	Title          string          `json:"title"`
	FirstReleaseDate string        `json:"first-release-date"`
	Disambiguation string          `json:"disambiguation"`
	ArtistCredit   []mbArtistCredit `json:"artist-credit"`
	PrimaryType    string          `json:"primary-type"`
}

type mbArtistCredit struct {
	Artist mbArtistRef `json:"artist"`
}

type mbArtistRef struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

type mbArtistDetail struct {
	ID             string     `json:"id"`
	Name           string     `json:"name"`
	SortName       string     `json:"sort-name"`
	Disambiguation string     `json:"disambiguation"`
	LifeSpan       mbLifeSpan `json:"life-span"`
}

type mbReleaseGroupDetail struct {
	ID               string          `json:"id"`
	Title            string          `json:"title"`
	FirstReleaseDate string          `json:"first-release-date"`
	ArtistCredit     []mbArtistCredit `json:"artist-credit"`
}

type coverArtResponse struct {
	Images []coverArtImage `json:"images"`
}

type coverArtImage struct {
	Image    string `json:"image"`
	Front    bool   `json:"front"`
}

func (p *MusicBrainzProvider) rateLimit() {
	elapsed := time.Since(p.lastRequest)
	if elapsed < time.Second {
		time.Sleep(time.Second - elapsed)
	}
	p.lastRequest = time.Now()
}

func (p *MusicBrainzProvider) fetch(ctx context.Context, url string, result interface{}) error {
	p.rateLimit()

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return fmt.Errorf("create request: %w", err)
	}
	req.Header.Set("User-Agent", p.userAgent)

	resp, err := p.httpClient.Do(req)
	if err != nil {
		return fmt.Errorf("fetch musicbrainz: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("musicbrainz api returned status %d", resp.StatusCode)
	}

	if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
		return fmt.Errorf("decode musicbrainz response: %w", err)
	}

	return nil
}

func (p *MusicBrainzProvider) Name() string {
	return "musicbrainz"
}

func (p *MusicBrainzProvider) Search(ctx context.Context, query string, opts SearchOptions) ([]MetadataSearchResult, error) {
	if opts.MediaType == "album" {
		return p.searchReleaseGroups(ctx, query)
	}
	return p.searchArtists(ctx, query)
}

func (p *MusicBrainzProvider) searchArtists(ctx context.Context, query string) ([]MetadataSearchResult, error) {
	url := fmt.Sprintf("%s/artist?query=%s&fmt=json&limit=20", p.baseURL, query)

	var resp mbArtistSearchResponse
	if err := p.fetch(ctx, url, &resp); err != nil {
		return nil, fmt.Errorf("search musicbrainz artists: %w", err)
	}

	var results []MetadataSearchResult
	for _, a := range resp.Artists {
		var year *int
		if len(a.LifeSpan.Begin) >= 4 {
			y := 0
			for _, c := range a.LifeSpan.Begin[:4] {
				if c >= '0' && c <= '9' {
					y = y*10 + int(c-'0')
				}
			}
			year = &y
		}

		extIDs, _ := json.Marshal(map[string]string{"musicbrainz": a.ID})

		results = append(results, MetadataSearchResult{
			ProviderID:    a.ID,
			Title:         a.Name,
			Year:          year,
			MediaType:     "music",
			OriginalTitle: a.Disambiguation,
			ExternalIDs:   extIDs,
		})
	}

	return results, nil
}

func (p *MusicBrainzProvider) searchReleaseGroups(ctx context.Context, query string) ([]MetadataSearchResult, error) {
	url := fmt.Sprintf("%s/release-group?query=%s&fmt=json&limit=20", p.baseURL, query)

	var resp mbReleaseGroupSearchResponse
	if err := p.fetch(ctx, url, &resp); err != nil {
		return nil, fmt.Errorf("search musicbrainz release groups: %w", err)
	}

	var results []MetadataSearchResult
	for _, rg := range resp.ReleaseGroups {
		var year *int
		if len(rg.FirstReleaseDate) >= 4 {
			y := 0
			for _, c := range rg.FirstReleaseDate[:4] {
				if c >= '0' && c <= '9' {
					y = y*10 + int(c-'0')
				}
			}
			year = &y
		}

		extIDs, _ := json.Marshal(map[string]string{"musicbrainz": rg.ID})
		artistName := ""
		if len(rg.ArtistCredit) > 0 {
			artistName = rg.ArtistCredit[0].Artist.Name
		}

		results = append(results, MetadataSearchResult{
			ProviderID:    rg.ID,
			Title:         rg.Title,
			Year:          year,
			MediaType:     "album",
			OriginalTitle: artistName,
			ExternalIDs:   extIDs,
		})
	}

	return results, nil
}

func (p *MusicBrainzProvider) GetDetails(ctx context.Context, id string) (*MetadataDetails, error) {
	artistURL := fmt.Sprintf("%s/artist/%s?fmt=json&inc=url-rels", p.baseURL, id)

	var artistDetail mbArtistDetail
	err := p.fetch(ctx, artistURL, &artistDetail)
	if err == nil {
		extIDs, _ := json.Marshal(map[string]string{"musicbrainz": id})

		metadata, _ := json.Marshal(map[string]interface{}{
			"sort_name":      artistDetail.SortName,
			"disambiguation": artistDetail.Disambiguation,
			"life_span_begin": artistDetail.LifeSpan.Begin,
			"life_span_end":   artistDetail.LifeSpan.End,
		})

		name := artistDetail.Name
		return &MetadataDetails{
			ProviderID:    id,
			Title:         name,
			ExternalIDs:   extIDs,
			Metadata:      metadata,
		}, nil
	}

	rgURL := fmt.Sprintf("%s/release-group/%s?fmt=json&inc=artist-credits", p.baseURL, id)
	var rgDetail mbReleaseGroupDetail
	if err := p.fetch(ctx, rgURL, &rgDetail); err != nil {
		return nil, fmt.Errorf("get musicbrainz details: %w", err)
	}

	extIDs, _ := json.Marshal(map[string]string{"musicbrainz": id})

	artistName := ""
	if len(rgDetail.ArtistCredit) > 0 {
		artistName = rgDetail.ArtistCredit[0].Artist.Name
	}

	metadata, _ := json.Marshal(map[string]interface{}{
		"first_release_date": rgDetail.FirstReleaseDate,
		"artist":             artistName,
	})

	var year *int
	if len(rgDetail.FirstReleaseDate) >= 4 {
		y := 0
		for _, c := range rgDetail.FirstReleaseDate[:4] {
			if c >= '0' && c <= '9' {
				y = y*10 + int(c-'0')
			}
		}
		year = &y
	}

	title := rgDetail.Title
	return &MetadataDetails{
		ProviderID: id,
		Title:      title,
		Year:       year,
		ExternalIDs: extIDs,
		Metadata:   metadata,
	}, nil
}

func (p *MusicBrainzProvider) GetImages(ctx context.Context, id string) ([]ImageResult, error) {
	coverURL := fmt.Sprintf("https://coverartarchive.org/release-group/%s", id)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, coverURL, nil)
	if err != nil {
		return nil, nil
	}
	req.Header.Set("User-Agent", p.userAgent)

	p.rateLimit()

	resp, err := p.httpClient.Do(req)
	if err != nil {
		return nil, nil
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, nil
	}

	var coverResp coverArtResponse
	if err := json.NewDecoder(resp.Body).Decode(&coverResp); err != nil {
		return nil, nil
	}

	for _, img := range coverResp.Images {
		if img.Front {
			return []ImageResult{{
				URL:  img.Image,
				Type: "cover",
			}}, nil
		}
	}

	if len(coverResp.Images) > 0 {
		return []ImageResult{{
			URL:  coverResp.Images[0].Image,
			Type: "cover",
		}}, nil
	}

	return nil, nil
}
