package api

import (
	"encoding/json"
	"fmt"
	"io/fs"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"arimelody-web/admin"
	"arimelody-web/global"
	"arimelody-web/controller"
	"arimelody-web/model"
)

func ServeRelease(release *model.Release) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // only allow authorised users to view hidden releases
        authorised := admin.GetSession(r) != nil
        if !authorised && !release.Visible {
            http.NotFound(w, r)
            return
        }

        type (
            Track struct {
                Title       string  `json:"title"`
                Description string  `json:"description"`
                Lyrics      string  `json:"lyrics"`
            }

            Credit struct {
                *model.Artist
                Role    string  `json:"role"`
                Primary bool    `json:"primary"`
            }

            Release struct {
                *model.Release
                Tracks  []Track             `json:"tracks"`
                Credits []Credit            `json:"credits"`
                Links   map[string]string   `json:"links"`
            }
        )

        response := Release{
            Release: release,
            Tracks: []Track{},
            Credits: []Credit{},
            Links: make(map[string]string),
        }

        if authorised || release.IsReleased() {
            // get credits
            credits, err := controller.GetReleaseCredits(global.DB, release.ID)
            if err != nil {
                fmt.Printf("FATAL: Failed to serve release %s: Credits: %s\n", release.ID, err)
                http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
                return
            }
            for _, credit := range credits {
                artist, err := controller.GetArtist(global.DB, credit.Artist.ID)
                if err != nil {
                    fmt.Printf("FATAL: Failed to serve release %s: Artists: %s\n", release.ID, err)
                    http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
                    return
                }

                response.Credits = append(response.Credits, Credit{
                    Artist: artist,
                    Role: credit.Role,
                    Primary: credit.Primary,
                })
            }

            // get tracks
            tracks, err := controller.GetReleaseTracks(global.DB, release.ID)
            if err != nil {
                fmt.Printf("FATAL: Failed to serve release %s: Tracks: %s\n", release.ID, err)
                http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
                return
            }
            for _, track := range tracks {
                response.Tracks = append(response.Tracks, Track{
                    Title: track.Title,
                    Description: track.Description,
                    Lyrics: track.Lyrics,
                })
            }

            // get links
            links, err := controller.GetReleaseLinks(global.DB, release.ID)
            if err != nil {
                fmt.Printf("FATAL: Failed to serve release %s: Links: %s\n", release.ID, err)
                http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
                return
            }
            for _, link := range links {
                response.Links[link.Name] = link.URL
            }
        }

		w.Header().Add("Content-Type", "application/json")
        encoder := json.NewEncoder(w)
        encoder.SetIndent("", "\t")
        err := encoder.Encode(response)
		if err != nil {
			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
			return
		}
	})
}

func ServeCatalog() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        releases, err := controller.GetAllReleases(global.DB, false, 0, true)
        if err != nil {
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            return
        }

        type Release struct {
            ID              string              `json:"id"`
            Title           string              `json:"title"`
            Artists         []string            `json:"artists"`
            ReleaseType     model.ReleaseType   `json:"type" db:"type"`
            ReleaseDate     time.Time           `json:"releaseDate" db:"release_date"`
            Artwork         string              `json:"artwork"`
            Buylink         string              `json:"buylink"`
            Copyright       string              `json:"copyright" db:"copyright"`
        }

        catalog := []Release{}
        authorised := admin.GetSession(r) != nil
        for _, release := range releases {
            if !release.Visible && !authorised {
                continue
            }
            artists := []string{}
            for _, credit := range release.Credits {
                if !credit.Primary { continue }
                artists = append(artists, credit.Artist.Name)
            }
            catalog = append(catalog, Release{
                ID: release.ID,
                Title: release.Title,
                Artists: artists,
                ReleaseType: release.ReleaseType,
                ReleaseDate: release.ReleaseDate,
                Artwork: release.Artwork,
                Buylink: release.Buylink,
                Copyright: release.Copyright,
            })
        }

        w.Header().Add("Content-Type", "application/json")
        encoder := json.NewEncoder(w)
        encoder.SetIndent("", "\t")
        err = encoder.Encode(catalog)
        if err != nil {
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            return
        }
    })
}

func CreateRelease() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Method != http.MethodPost {
            http.NotFound(w, r)
            return
        }

        var release model.Release
        err := json.NewDecoder(r.Body).Decode(&release)
        if err != nil {
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
            return
        }

        if release.ID == "" {
            http.Error(w, "Release ID cannot be empty\n", http.StatusBadRequest)
            return
        }
        
        if release.Title == "" { release.Title = release.ID }
        if release.ReleaseType == "" { release.ReleaseType = model.Single }
        
        if release.ReleaseDate != time.Unix(0, 0) {
            release.ReleaseDate = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC)
        }

        if release.Artwork == "" { release.Artwork = "/img/default-cover-art.png" }

        err = controller.CreateRelease(global.DB, &release)
        if err != nil {
            if strings.Contains(err.Error(), "duplicate key") {
                http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest)
                return
            }
            fmt.Printf("FATAL: Failed to create release %s: %s\n", release.ID, err)
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
            return
        }

        w.Header().Add("Content-Type", "application/json")
        w.WriteHeader(http.StatusCreated)
        encoder := json.NewEncoder(w)
        encoder.SetIndent("", "\t")
        err = encoder.Encode(release)
        if err != nil {
            fmt.Printf("WARN: Release %s created, but failed to send JSON response: %s\n", release.ID, err)
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        }
    })
}

func UpdateRelease(release *model.Release) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path == "/" {
            http.NotFound(w, r)
            return
        }

        segments := strings.Split(r.URL.Path[1:], "/")

        if len(segments) == 2 {
            switch segments[1] {
            case "tracks":
                UpdateReleaseTracks(release).ServeHTTP(w, r)
            case "credits":
                UpdateReleaseCredits(release).ServeHTTP(w, r)
            case "links":
                UpdateReleaseLinks(release).ServeHTTP(w, r)
            }
            return
        }
        
        if len(segments) > 2 {
            http.NotFound(w, r)
            return
        }

        err := json.NewDecoder(r.Body).Decode(&release)
        if err != nil {
            fmt.Printf("WARN: Failed to update release %s: %s\n", release.ID, err)
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
            return
        }

        if release.Artwork == "" {
            release.Artwork = "/img/default-cover-art.png"
        } else {
            if strings.Contains(release.Artwork, ";base64,") {
                var artworkDirectory = filepath.Join("uploads", "musicart")
                filename, err := HandleImageUpload(&release.Artwork, artworkDirectory, release.ID)

                // clean up files with this ID and different extensions
                err = filepath.Walk(artworkDirectory, func(path string, info fs.FileInfo, err error) error {
                    if path == filepath.Join(artworkDirectory, filename) { return nil }

                    withoutExt := strings.TrimSuffix(path, filepath.Ext(path))
                    if withoutExt != filepath.Join(artworkDirectory, release.ID) { return nil }

                    return os.Remove(path)
                })
                if err != nil {
                    fmt.Printf("WARN: Error while cleaning up artwork files: %s\n", err)
                }

                release.Artwork = fmt.Sprintf("/uploads/musicart/%s", filename)
            }
        }

        err = controller.UpdateRelease(global.DB, release)
        if err != nil {
            if strings.Contains(err.Error(), "no rows") {
                http.NotFound(w, r)
                return
            }
            fmt.Printf("FATAL: Failed to update release %s: %s\n", release.ID, err)
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        }
    })
}

func UpdateReleaseTracks(release *model.Release) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        var trackIDs = []string{}
        err := json.NewDecoder(r.Body).Decode(&trackIDs)
        if err != nil {
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
            return
        }

        err = controller.UpdateReleaseTracks(global.DB, release.ID, trackIDs)
        if err != nil {
            if strings.Contains(err.Error(), "no rows") {
                http.NotFound(w, r)
                return
            }
            fmt.Printf("FATAL: Failed to update tracks for %s: %s\n", release.ID, err)
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        }
    })
}

func UpdateReleaseCredits(release *model.Release) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        type creditJSON struct {
            Artist  string
            Role    string
            Primary bool
        }
        var data []creditJSON
        err := json.NewDecoder(r.Body).Decode(&data)
        if err != nil {
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
            return
        }

        var credits []*model.Credit
        for _, credit := range data {
            credits = append(credits, &model.Credit{
                Artist: model.Artist{
                    ID: credit.Artist,
                },
                Role: credit.Role,
                Primary: credit.Primary,
            })
        }

        err = controller.UpdateReleaseCredits(global.DB, release.ID, credits)
        if err != nil {
            if strings.Contains(err.Error(), "duplicate key") {
                http.Error(w, "Artists may only be credited once\n", http.StatusBadRequest)
                return
            }
            if strings.Contains(err.Error(), "no rows") {
                http.NotFound(w, r)
                return
            }
            fmt.Printf("FATAL: Failed to update links for %s: %s\n", release.ID, err)
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        }
    })
}

func UpdateReleaseLinks(release *model.Release) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.Method != http.MethodPut {
            http.NotFound(w, r)
            return
        }

        var links = []*model.Link{}
        err := json.NewDecoder(r.Body).Decode(&links)
        if err != nil {
            http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
            return
        }

        err = controller.UpdateReleaseLinks(global.DB, release.ID, links)
        if err != nil {
            if strings.Contains(err.Error(), "no rows") {
                http.NotFound(w, r)
                return
            }
            fmt.Printf("FATAL: Failed to update links for %s: %s\n", release.ID, err)
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        }
    })
}

func DeleteRelease(release *model.Release) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        err := controller.DeleteRelease(global.DB, release.ID)
        if err != nil {
            if strings.Contains(err.Error(), "no rows") {
                http.NotFound(w, r)
                return
            }
            fmt.Printf("FATAL: Failed to delete release %s: %s\n", release.ID, err)
            http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
        }
    })
}
