package submit

import (
	"bytes"
	"crypto/rand"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"time"

	"pinscrape-allgo/internal/config"
	"pinscrape-allgo/internal/csvgen"
)

const (
	indexNowAPI     = "https://indexnow.amazonbot.amazon/indexnow"
	host            = "storage.googleapis.com"
	maxURLsPerBatch = 10000
	maxRetries      = 3
)

type Result struct {
	Bucket    string `json:"bucket"`
	Foldering string `json:"foldering"`
	URLCount  int    `json:"url_count"`
	Submit    string `json:"submit"`
	Notes     string `json:"notes"`
	Source    string `json:"source"`
}

func generateKey(length int) string {
	const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
	b := make([]byte, length)
	rand.Read(b)
	for i := range b {
		b[i] = chars[int(b[i])%len(chars)]
	}
	return string(b)
}

func resolveGcloud() string {
	var candidates []string
	if la := os.Getenv("LOCALAPPDATA"); la != "" {
		candidates = append(candidates, filepath.Join(la, "Google", "Cloud SDK", "google-cloud-sdk", "bin", "gcloud.cmd"))
	}
	if pf := os.Getenv("ProgramFiles"); pf != "" {
		candidates = append(candidates, filepath.Join(pf, "Google", "Cloud SDK", "google-cloud-sdk", "bin", "gcloud.cmd"))
	}
	for _, c := range candidates {
		if _, err := os.Stat(c); err == nil {
			return c
		}
	}
	return "gcloud"
}

func logLine(reportDir, bucket, message string) {
	os.MkdirAll(reportDir, 0755)
	ts := time.Now().UTC().Format("2006-01-02T15:04:05Z")
	line := fmt.Sprintf("[%s] %s", ts, message)
	log.Printf("  %s", line)
	f, err := os.OpenFile(filepath.Join(reportDir, bucket+"_indexnow_log.txt"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err == nil {
		defer f.Close()
		f.WriteString(line + "\n")
	}
}

func fetchURL(url string) ([]byte, error) {
	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", "Mozilla/5.0")
	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return io.ReadAll(resp.Body)
}

func extractSitemapURLs(content []byte) []string {
	var urls []string
	s := string(content)
	for {
		i := strings.Index(s, "<loc>")
		if i == -1 {
			break
		}
		rest := s[i+5:]
		j := strings.Index(rest, "</loc>")
		if j == -1 {
			break
		}
		u := strings.TrimSpace(rest[:j])
		if u != "" {
			urls = append(urls, u)
		}
		s = rest[j+6:]
	}
	return urls
}

func loadSubmitted(reportDir, bucket string) map[string]bool {
	path := filepath.Join(reportDir, bucket+"_submitted_urls.txt")
	data, err := os.ReadFile(path)
	if err != nil {
		return map[string]bool{}
	}
	out := map[string]bool{}
	for _, line := range strings.Split(string(data), "\n") {
		line = strings.TrimSpace(line)
		if line != "" {
			out[line] = true
		}
	}
	return out
}

func saveSubmitted(reportDir, bucket string, urls []string) {
	path := filepath.Join(reportDir, bucket+"_submitted_urls.txt")
	f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return
	}
	defer f.Close()
	for _, u := range urls {
		f.WriteString(u + "\n")
	}
}

func submitIndexNow(key, keyLocation string, urlList []string) (int, string) {
	payload, _ := json.Marshal(map[string]interface{}{
		"host":        host,
		"key":         key,
		"keyLocation": keyLocation,
		"urlList":     urlList,
	})
	for attempt := 1; attempt <= maxRetries; attempt++ {
		req, err := http.NewRequest(http.MethodPost, indexNowAPI, bytes.NewReader(payload))
		if err != nil {
			return 0, err.Error()
		}
		req.Header.Set("Content-Type", "application/json; charset=utf-8")
		client := &http.Client{Timeout: 30 * time.Second}
		resp, err := client.Do(req)
		if err != nil {
			if attempt < maxRetries {
				time.Sleep(10 * time.Second)
				continue
			}
			return 0, err.Error()
		}
		body, _ := io.ReadAll(resp.Body)
		resp.Body.Close()
		if resp.StatusCode == 403 && attempt < maxRetries {
			time.Sleep(10 * time.Second)
			continue
		}
		if strings.Contains(string(body), "SiteVerificationNotCompleted") && attempt < maxRetries {
			time.Sleep(10 * time.Second)
			continue
		}
		if resp.StatusCode >= 400 {
			return resp.StatusCode, string(body)
		}
		return resp.StatusCode, ""
	}
	return 0, "Max retries exceeded"
}

func uploadKey(gcloud, localPath, bucket string) (bool, string) {
	var cmd *exec.Cmd
	args := []string{"storage", "cp", localPath, "gs://" + bucket + "/", "--quiet"}
	if runtime.GOOS == "windows" {
		cmd = exec.Command("cmd", append([]string{"/c", gcloud}, args...)...)
	} else {
		cmd = exec.Command(gcloud, args...)
	}
	var errb strings.Builder
	cmd.Stderr = &errb
	if err := cmd.Run(); err != nil {
		return false, strings.TrimSpace(errb.String())
	}
	return true, ""
}

// Run submits all new article URLs per bucket to IndexNow (parity with
// scripts/submit_now.py).
func Run(cfg *config.Config, csvPath string) ([]Result, error) {
	gcloud := resolveGcloud()
	mappings, err := csvgen.ReadMappings(cfg, csvPath)
	if err != nil {
		return nil, err
	}

	reportDir := cfg.Path("submit", "Report PY")
	keyDir := cfg.Path("submit", "key")
	os.MkdirAll(reportDir, 0755)
	os.MkdirAll(keyDir, 0755)

	var results []Result
	for _, m := range mappings {
		entry := Result{Bucket: m.Bucket, Foldering: m.Foldering, Source: m.Source}
		reportDirPath := cfg.Path(cfg.ExportFolder, m.Foldering)
		if st, err := os.Stat(reportDirPath); err != nil || !st.IsDir() {
			entry.Submit = "SKIP"
			entry.Notes = "export folder not found"
			results = append(results, entry)
			continue
		}

		key := generateKey(36)
		keyFile := filepath.Join(keyDir, key+".txt")
		if err := os.WriteFile(keyFile, []byte(key), 0644); err != nil {
			entry.Submit = "FAILED"
			entry.Notes = "key write failed: " + err.Error()
			results = append(results, entry)
			continue
		}
		logLine(reportDir, m.Bucket, "Key generated: "+key)

		ok, errMsg := uploadKey(gcloud, keyFile, m.Bucket)
		if !ok {
			entry.Submit = "FAILED"
			entry.Notes = "key upload failed: " + errMsg
			results = append(results, entry)
			continue
		}
		logLine(reportDir, m.Bucket, "Key uploaded to GCS")

		keyLocation := fmt.Sprintf("https://%s/%s/%s.txt", host, m.Bucket, key)

		sitemapURL := fmt.Sprintf("https://%s/%s/sitemap.xml", host, m.Bucket)
		content, err := fetchURL(sitemapURL)
		if err != nil {
			entry.Submit = "FAILED"
			entry.Notes = "sitemap fetch failed: " + err.Error()
			results = append(results, entry)
			continue
		}
		urls := extractSitemapURLs(content)
		submitted := loadSubmitted(reportDir, m.Bucket)

		var newURLs []string
		for _, u := range urls {
			if !submitted[u] {
				newURLs = append(newURLs, u)
			}
		}
		sort.Strings(newURLs)

		if len(newURLs) == 0 {
			entry.Submit = "SKIP"
			entry.Notes = fmt.Sprintf("no new urls (%d total)", len(urls))
			results = append(results, entry)
			continue
		}

		toSubmit := newURLs
		if len(toSubmit) > maxURLsPerBatch {
			toSubmit = toSubmit[:maxURLsPerBatch]
		}
		code, errBody := submitIndexNow(key, keyLocation, toSubmit)
		if code >= 200 && code < 300 {
			saveSubmitted(reportDir, m.Bucket, toSubmit)
			entry.URLCount = len(toSubmit)
			entry.Submit = "SUCCESS"
			logLine(reportDir, m.Bucket, fmt.Sprintf("Submitted %d urls (code %d)", len(toSubmit), code))
		} else {
			entry.Submit = "FAILED"
			entry.Notes = fmt.Sprintf("indexnow code %d: %s", code, errBody)
			logLine(reportDir, m.Bucket, entry.Notes)
		}
		results = append(results, entry)
	}

	return results, nil
}
