package handler

import (
	"encoding/xml"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"podstalk/store"
)

type ImportHandler struct {
	Store     *store.Store
	Tpl       *Templates
	UploadDir string
}

type importFeed struct {
	XMLName xml.Name      `xml:"rss"`
	Channel importChannel `xml:"channel"`
}

type importChannel struct {
	Title       string             `xml:"title"`
	Description string             `xml:"description"`
	Image       *importImage       `xml:"image"`
	ItunesImage *importItunesImage `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd image"`
	Items       []importItem       `xml:"item"`
}

type importItem struct {
	Title       string             `xml:"title"`
	Description string             `xml:"description"`
	Enclosure   importEnclosure    `xml:"enclosure"`
	PubDate     string             `xml:"pubDate"`
	GUID        string             `xml:"guid"`
	ItunesImage *importItunesImage `xml:"http://www.itunes.com/dtds/podcast-1.0.dtd image"`
}

type importEnclosure struct {
	URL    string `xml:"url,attr"`
	Length string `xml:"length,attr"`
	Type   string `xml:"type,attr"`
}

type importImage struct {
	URL string `xml:"url"`
}

type importItunesImage struct {
	Href string `xml:"href,attr"`
}

type ImportEpisodeView struct {
	Index                int
	Title                string
	Description          string
	DescriptionTruncated string
	PubDate              string
	AudioURL             string
}

func truncateString(s string, maxLen int) string {
	runes := []rune(s)
	if len(runes) <= maxLen {
		return s
	}
	return string(runes[:maxLen]) + "…"
}

func (h *ImportHandler) ServeImport(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		h.Tpl.Render(w, "import", map[string]any{
			"Fetched": false,
		})
		return
	}

	if err := r.ParseForm(); err != nil {
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}

	rssURL := strings.TrimSpace(r.FormValue("rss_url"))
	if rssURL == "" {
		h.Tpl.Render(w, "import", map[string]any{
			"Fetched": false,
			"Error":   "RSS URL is required.",
		})
		return
	}

	if r.FormValue("import") == "1" {
		h.importSelected(w, r, rssURL)
		return
	}

	episodes, err := h.fetchRSS(rssURL)
	if err != nil {
		h.Tpl.Render(w, "import", map[string]any{
			"Fetched": false,
			"Error":   fmt.Sprintf("Failed to fetch RSS: %v", err),
			"RSSURL":  rssURL,
		})
		return
	}

	if len(episodes) == 0 {
		h.Tpl.Render(w, "import", map[string]any{
			"Fetched": false,
			"Error":   "No episodes found in the RSS feed.",
			"RSSURL":  rssURL,
		})
		return
	}

	h.Tpl.Render(w, "import", map[string]any{
		"Fetched":  true,
		"RSSURL":   rssURL,
		"Episodes": episodes,
	})
}

func (h *ImportHandler) fetchRSS(rssURL string) ([]ImportEpisodeView, error) {
	resp, err := http.Get(rssURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
	}

	var feed importFeed
	dec := xml.NewDecoder(resp.Body)
	if err := dec.Decode(&feed); err != nil {
		return nil, err
	}

	var episodes []ImportEpisodeView
	for i, item := range feed.Channel.Items {
		pubDate := item.PubDate
		if t, err := parseRSSDate(pubDate); err == nil {
			pubDate = t.Format("Jan 2, 2006")
		}

		episodes = append(episodes, ImportEpisodeView{
			Index:                i,
			Title:                item.Title,
			Description:          item.Description,
			DescriptionTruncated: truncateString(item.Description, 150),
			PubDate:              pubDate,
			AudioURL:             item.Enclosure.URL,
		})
	}

	return episodes, nil
}

func (h *ImportHandler) importSelected(w http.ResponseWriter, r *http.Request, rssURL string) {
	userID := UserIDFromContext(r)

	selected := r.Form["selected"]
	if len(selected) == 0 {
		http.Redirect(w, r, "/import?error=no+episodes+selected", http.StatusSeeOther)
		return
	}

	selectedSet := make(map[int]bool)
	for _, s := range selected {
		idx, err := strconv.Atoi(s)
		if err == nil {
			selectedSet[idx] = true
		}
	}

	resp, err := http.Get(rssURL)
	if err != nil {
		http.Redirect(w, r, "/import?error=failed+to+fetch+rss", http.StatusSeeOther)
		return
	}
	defer resp.Body.Close()

	var feed importFeed
	dec := xml.NewDecoder(resp.Body)
	if err := dec.Decode(&feed); err != nil {
		http.Redirect(w, r, "/import?error=failed+to+parse+rss", http.StatusSeeOther)
		return
	}

	imported := 0
	var errors []string

	for i, item := range feed.Channel.Items {
		if !selectedSet[i] {
			continue
		}

		audioPath, err := h.downloadFile(item.Enclosure.URL, "audio")
		if err != nil {
			errors = append(errors, fmt.Sprintf("%s: audio download failed: %v", item.Title, err))
			continue
		}

		imagePath := ""
		imageURL := h.episodeImageURL(&item, &feed.Channel)
		if imageURL != "" {
			if p, err := h.downloadFile(imageURL, "images"); err == nil {
				imagePath = p
			}
		}

		publishedAt := time.Now()
		if t, err := parseRSSDate(item.PubDate); err == nil {
			publishedAt = t
		}

		_, err = h.Store.CreateEpisode(userID, item.Title, item.Description, audioPath, imagePath, publishedAt)
		if err != nil {
			errors = append(errors, fmt.Sprintf("%s: database error: %v", item.Title, err))
			h.removeFile(audioPath)
			if imagePath != "" {
				h.removeFile(imagePath)
			}
			continue
		}

		imported++
	}

	if imported > 0 {
		http.Redirect(w, r, fmt.Sprintf("/?success=%d+episodes+imported+successfully", imported), http.StatusSeeOther)
		return
	}

	http.Redirect(w, r, "/import?error=no+episodes+could+be+imported", http.StatusSeeOther)
}

func (h *ImportHandler) episodeImageURL(item *importItem, channel *importChannel) string {
	if item.ItunesImage != nil && item.ItunesImage.Href != "" {
		return item.ItunesImage.Href
	}
	if channel.ItunesImage != nil && channel.ItunesImage.Href != "" {
		return channel.ItunesImage.Href
	}
	if channel.Image != nil && channel.Image.URL != "" {
		return channel.Image.URL
	}
	return ""
}

func (h *ImportHandler) downloadFile(url, category string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("HTTP %d", resp.StatusCode)
	}

	filename := filepath.Base(url)
	if filename == "" || filename == "." || filename == "/" {
		filename = "file"
	}
	base := sanitizeFilename(filename)
	ext := filepath.Ext(base)
	if ext == "" {
		ct := resp.Header.Get("Content-Type")
		switch {
		case strings.HasPrefix(ct, "audio/"):
			ext = ".mp3"
		case strings.HasPrefix(ct, "image/png"):
			ext = ".png"
		case strings.HasPrefix(ct, "image/"):
			ext = ".jpg"
		default:
			ext = ".bin"
		}
	} else {
		base = base[:len(base)-len(ext)]
	}
	filename = fmt.Sprintf("%d_%s%s", time.Now().UnixNano(), base, ext)

	catDir := filepath.Join(h.UploadDir, category)
	if err := os.MkdirAll(catDir, 0755); err != nil {
		return "", err
	}

	dst, err := os.Create(filepath.Join(catDir, filename))
	if err != nil {
		return "", err
	}
	defer dst.Close()

	if _, err := io.Copy(dst, resp.Body); err != nil {
		return "", err
	}

	return category + "/" + filename, nil
}

func (h *ImportHandler) removeFile(relPath string) {
	if relPath == "" {
		return
	}
	os.Remove(filepath.Join(h.UploadDir, relPath))
}

func parseRSSDate(s string) (time.Time, error) {
	formats := []string{
		time.RFC1123Z,
		time.RFC1123,
		time.RFC822Z,
		time.RFC822,
		time.RFC3339,
		"Mon, 02 Jan 2006 15:04:05 -0700",
		"Mon, 2 Jan 2006 15:04:05 -0700",
		"2006-01-02T15:04:05Z",
		"2006-01-02T15:04:05-07:00",
	}
	for _, f := range formats {
		if t, err := time.Parse(f, s); err == nil {
			return t, nil
		}
	}
	return time.Now(), nil
}
