package api

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/ponzischeme89/memby/server/internal/sonarr"
	"github.com/ponzischeme89/memby/server/internal/store"
)

type myShowResponse struct {
	store.UserShow
	SonarrStatus string     `json:"sonarrStatus"`
	NextEpisode  *time.Time `json:"nextEpisode,omitempty"`
	Lifecycle    string     `json:"lifecycle"`
	Monitored    bool       `json:"monitored"`
}

type myShowsResponse struct {
	Shows []myShowResponse `json:"shows"`
}

type notificationsResponse struct {
	Notifications []store.UserNotification      `json:"notifications"`
	Preferences   store.NotificationPreferences `json:"preferences"`
}

func (s *Server) handleMyShows(w http.ResponseWriter, r *http.Request, sess store.Session) {
	switch r.Method {
	case http.MethodGet:
		s.listMyShows(w, r, sess)
	case http.MethodPost:
		var show store.UserShow
		if !decodeJSON(w, r, &show) {
			return
		}
		show.ItemID = strings.TrimSpace(show.ItemID)
		show.Title = strings.TrimSpace(show.Title)
		if show.ItemID == "" || show.Title == "" {
			writeError(w, http.StatusBadRequest, "itemId and title are required")
			return
		}
		if err := s.store.SaveUserShow(r.Context(), sess.EmbyUserID, show); err != nil {
			s.loggerFor(r.Context()).Error("save user show failed", "error", err)
			writeError(w, http.StatusInternalServerError, "could not save show")
			return
		}
		s.listMyShows(w, r, sess)
	default:
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}

func (s *Server) handleMyShow(w http.ResponseWriter, r *http.Request, sess store.Session) {
	itemID := strings.TrimSpace(r.PathValue("id"))
	if itemID == "" {
		writeError(w, http.StatusBadRequest, "show id is required")
		return
	}
	if err := s.store.DeleteUserShow(r.Context(), sess.EmbyUserID, itemID); err != nil {
		s.loggerFor(r.Context()).Error("delete user show failed", "error", err)
		writeError(w, http.StatusInternalServerError, "could not remove show")
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (s *Server) listMyShows(w http.ResponseWriter, r *http.Request, sess store.Session) {
	saved, err := s.store.UserShows(r.Context(), sess.EmbyUserID)
	if err != nil {
		s.loggerFor(r.Context()).Error("list user shows failed", "error", err)
		writeError(w, http.StatusInternalServerError, "could not load shows")
		return
	}
	sonarrSeries := []sonarr.Series{}
	if s.sonarr != nil {
		if value, seriesErr := s.sonarrSeriesCatalogue(r.Context()); seriesErr == nil {
			sonarrSeries = value
		} else {
			s.loggerFor(r.Context()).Warn("Sonarr status unavailable for My Shows", "error", seriesErr)
		}
	}
	result := make([]myShowResponse, 0, len(saved))
	for _, show := range saved {
		matched := matchSonarrSeries(show, sonarrSeries)
		response := myShowResponse{
			UserShow:     show,
			SonarrStatus: "Not found",
			Lifecycle:    "Unknown",
		}
		if matched != nil {
			response.SonarrStatus = sonarrStatus(*matched)
			response.NextEpisode = matched.NextAiring
			response.Lifecycle = seriesLifecycle(matched.Status)
			response.Monitored = matched.Monitored
		}
		result = append(result, response)
	}
	writeJSON(w, http.StatusOK, myShowsResponse{Shows: result})
}

func matchSonarrSeries(show store.UserShow, all []sonarr.Series) *sonarr.Series {
	key := normalizedShowTitle(show.Title)
	for i := range all {
		candidate := &all[i]
		if normalizedShowTitle(candidate.Title) != key {
			continue
		}
		if show.Year == nil || candidate.Year == 0 || candidate.Year == *show.Year {
			return candidate
		}
	}
	return nil
}

func normalizedShowTitle(value string) string {
	return strings.Map(func(r rune) rune {
		if r >= 'A' && r <= 'Z' {
			return r + ('a' - 'A')
		}
		if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
			return r
		}
		return -1
	}, value)
}

func seriesLifecycle(status string) string {
	switch strings.ToLower(status) {
	case "continuing", "upcoming":
		return "Continuing"
	case "ended", "deleted":
		return "Cancelled"
	default:
		if status == "" {
			return "Unknown"
		}
		return strings.ToUpper(status[:1]) + strings.ToLower(status[1:])
	}
}

func isContinuingSonarrStatus(status string) bool {
	return strings.EqualFold(status, "continuing") || strings.EqualFold(status, "upcoming")
}

func sonarrStatus(series sonarr.Series) string {
	if series.Monitored {
		return "Monitored"
	}
	return "Not monitored"
}

func (s *Server) handleNotifications(w http.ResponseWriter, r *http.Request, sess store.Session) {
	prefs, err := s.store.NotificationPreferences(r.Context(), sess.EmbyUserID)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "could not load notification preferences")
		return
	}
	if r.Method == http.MethodPut {
		if !decodeJSON(w, r, &prefs) {
			return
		}
		if err := s.store.SetNotificationPreferences(r.Context(), sess.EmbyUserID, prefs); err != nil {
			writeError(w, http.StatusInternalServerError, "could not save notification preferences")
			return
		}
	}
	if prefs.Enabled && prefs.ShowReturnAlerts {
		s.syncReturnNotifications(r, sess, prefs)
	}
	notifications, err := s.store.UserNotifications(r.Context(), sess.EmbyUserID)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "could not load notifications")
		return
	}
	writeJSON(w, http.StatusOK, notificationsResponse{
		Notifications: notifications,
		Preferences:   prefs,
	})
}

func (s *Server) syncReturnNotifications(
	r *http.Request, sess store.Session, prefs store.NotificationPreferences,
) {
	if s.sonarr == nil {
		return
	}
	shows, err := s.store.UserShows(r.Context(), sess.EmbyUserID)
	if err != nil {
		return
	}
	all, err := s.sonarrSeriesCatalogue(r.Context())
	if err != nil {
		return
	}
	now := time.Now()
	until := now.AddDate(0, 0, prefs.LeadDays)
	for _, show := range shows {
		series := matchSonarrSeries(show, all)
		if series == nil || series.NextAiring == nil ||
			series.NextAiring.Before(now) || series.NextAiring.After(until) {
			continue
		}
		days := int(series.NextAiring.Sub(now).Hours()/24) + 1
		message := fmt.Sprintf("%s returns in %d days.", show.Title, days)
		if days <= 1 {
			message = show.Title + " returns tomorrow."
		} else if days == 7 {
			message = show.Title + " returns next week."
		}
		sourceKey := "show-return:" + show.ItemID + ":" + series.NextAiring.UTC().Format("2006-01-02")
		_ = s.store.UpsertNotification(
			r.Context(), sess.EmbyUserID, sourceKey, "show-return", show.ItemID,
			"New episode coming", message, series.NextAiring,
		)
	}
}

func (s *Server) handleNotificationAction(
	w http.ResponseWriter, r *http.Request, sess store.Session,
) {
	id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
	if err != nil || id < 1 {
		writeError(w, http.StatusBadRequest, "invalid notification id")
		return
	}
	switch r.PathValue("action") {
	case "read":
		err = s.store.MarkNotificationRead(r.Context(), sess.EmbyUserID, id)
	case "dismiss":
		err = s.store.DismissNotification(r.Context(), sess.EmbyUserID, id)
	default:
		writeError(w, http.StatusNotFound, "unknown notification action")
		return
	}
	if err != nil {
		writeError(w, http.StatusInternalServerError, "could not update notification")
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func decodeJSON(w http.ResponseWriter, r *http.Request, out any) bool {
	decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(out); err != nil {
		writeError(w, http.StatusBadRequest, "invalid request")
		return false
	}
	return true
}
