package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"math/rand"
	"mime/multipart"
	"net/http"
	urlpkg "net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

var applovinUserAgents = []string{
	"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
	"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
	"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0",
	"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
	"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15",
	"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:131.0) Gecko/20100101 Firefox/131.0",
	"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
}

func pickApplovinUserAgent(randomize bool) string {
	if !randomize {
		return applovinUserAgents[0]
	}
	return applovinUserAgents[rand.Intn(len(applovinUserAgents))]
}

func ApplovinPage(w http.ResponseWriter, r *http.Request) {
	body := `
<h2>AppLovin Playable Preview (QR)</h2>
<p class="hint" style="margin-top:0;">Upload HTML files to p.applov.in and get QR codes to scan with the AppLovin Playable Preview app.</p>

<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
  <button id="pick" class="secondary">Add files...</button>
  <button id="clear" class="secondary" style="display:none;">Clear</button>
  <span id="fileName" class="hint">(no files)</span>
</div>
<div id="fileList" style="margin-bottom:8px;font-size:12px;opacity:0.85;"></div>
<div style="display:flex;gap:8px;">
  <button id="upload">Upload to AppLovin</button>
  <button id="saveAll" class="secondary" style="display:none;">Save All QRs...</button>
  <button id="regenAll" class="secondary" style="display:none;">Regenerate QRs</button>
</div>

<div id="status" style="margin-top:12px;min-height:20px;"></div>
<div id="results" style="margin-top:12px;display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;"></div>

<style>
  .result { padding:10px; background:#252526; border-left:3px solid #007fd4; word-break:break-all; }
  .result-actions { margin-top:8px; display:flex; gap:8px; flex-wrap:wrap; }
</style>

<script>
const pickBtn = document.getElementById('pick');
const clearBtn = document.getElementById('clear');
const uploadBtn = document.getElementById('upload');
const fileNameEl = document.getElementById('fileName');
const fileListEl = document.getElementById('fileList');
const statusEl = document.getElementById('status');
const resultsEl = document.getElementById('results');
const saveAllBtn = document.getElementById('saveAll');
const regenAllBtn = document.getElementById('regenAll');
let selectedPaths = [];
let resultItems = [];

function basename(p) {
  const i = Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\'));
  return i >= 0 ? p.slice(i + 1) : p;
}
function renderSelection() {
  if (!selectedPaths.length) {
    fileNameEl.textContent = '(no files)';
    fileListEl.innerHTML = '';
    clearBtn.style.display = 'none';
    return;
  }
  fileNameEl.textContent = selectedPaths.length + ' file' + (selectedPaths.length === 1 ? '' : 's');
  fileListEl.innerHTML = selectedPaths.map(p => '<div>' + escapeHtml(basename(p)) + '</div>').join('');
  clearBtn.style.display = '';
}

saveAllBtn.addEventListener('click', async () => {
  if (!resultItems.length) return;
  saveAllBtn.disabled = true;
  try {
    const r = await fetch('/api/applovin/saveAllQrs', {
      method: 'POST',
      headers: {'Content-Type':'application/json'},
      body: JSON.stringify({ items: resultItems }),
    });
    const j = await r.json();
    statusEl.innerHTML = j.ok
      ? '<span class="ok">' + escapeHtml(j.message || 'Saved.') + '</span>'
      : '<span class="err">' + escapeHtml(j.message || 'Save failed.') + '</span>';
  } finally {
    saveAllBtn.disabled = false;
  }
});

regenAllBtn.addEventListener('click', () => {
  resultsEl.querySelectorAll('img[data-qr]').forEach(img => {
    const base = img.getAttribute('data-qr');
    img.src = base + '&_=' + Date.now();
  });
});

clearBtn.addEventListener('click', () => {
  selectedPaths = [];
  renderSelection();
});

pickBtn.addEventListener('click', async () => {
  const r = await fetch('/api/applovin/pick', { method: 'POST' });
  const j = await r.json();
  if (j.files && j.files.length) {
    const added = j.files.map(f => f.path).filter(p => !selectedPaths.includes(p));
    selectedPaths = selectedPaths.concat(added);
    renderSelection();
  }
});

uploadBtn.addEventListener('click', async () => {
  statusEl.textContent = '';
  resultsEl.innerHTML = '';
  if (!selectedPaths.length) {
    statusEl.innerHTML = '<span class="err">Pick at least one HTML file first.</span>';
    return;
  }
  uploadBtn.disabled = true;
  resultItems = [];
  saveAllBtn.style.display = 'none';
  regenAllBtn.style.display = 'none';
  try {
    const res = await fetch('/api/applovin/upload', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ paths: selectedPaths }),
    });
    if (!res.ok || !res.body) {
      statusEl.innerHTML = '<span class="err">Upload failed.</span>';
      uploadBtn.disabled = false;
      return;
    }
    const reader = res.body.getReader();
    const dec = new TextDecoder();
    let buf = '';
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      buf += dec.decode(value, { stream: true });
      let nl;
      while ((nl = buf.indexOf('\n')) >= 0) {
        const line = buf.slice(0, nl); buf = buf.slice(nl + 1);
        if (!line.trim()) continue;
        handle(JSON.parse(line));
      }
    }
  } catch (e) {
    statusEl.innerHTML = '<span class="err">' + (e.message || e) + '</span>';
  } finally {
    uploadBtn.disabled = false;
  }
});

function escapeHtml(s){
  return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}

function handle(m) {
  if (m.type === 'status') {
    statusEl.textContent = m.message;
  } else if (m.type === 'fileError') {
    const wrap = document.createElement('div');
    wrap.className = 'result';
    wrap.innerHTML = '<div><strong>' + escapeHtml(m.name) + '</strong></div>' +
      '<div class="err" style="margin-top:6px;">' + escapeHtml(m.message) + '</div>';
    resultsEl.appendChild(wrap);
  } else if (m.type === 'fileResult') {
    const wrap = document.createElement('div');
    wrap.className = 'result';
    wrap.innerHTML =
      '<div style="font-weight:600;margin-bottom:8px;">' + escapeHtml(m.name) + '</div>' +
      '<div style="text-align:center;margin-bottom:8px;">' +
        '<img src="' + m.qrImg + '" data-qr="' + m.qrImg + '" style="background:#fff;padding:8px;border-radius:4px;max-width:100%;" />' +
      '</div>' +
      '<div style="font-size:11px;opacity:0.8;">Hash: ' + escapeHtml(m.hash) + '</div>';
    const actions = document.createElement('div');
    actions.className = 'result-actions';
    const openBtn = document.createElement('button');
    openBtn.textContent = 'Open';
    openBtn.className = 'secondary';
    openBtn.onclick = () => fetch('/api/open', {method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:m.previewUrl})});
    const saveQr = document.createElement('button');
    saveQr.textContent = 'Save QR';
    saveQr.className = 'secondary';
    saveQr.onclick = async () => {
      saveQr.disabled = true;
      try {
        const r = await fetch('/api/applovin/saveQr', {
          method: 'POST',
          headers: {'Content-Type':'application/json'},
          body: JSON.stringify({ name: m.name, url: m.qrImg }),
        });
        const j = await r.json();
        statusEl.innerHTML = j.ok
          ? '<span class="ok">' + escapeHtml(j.message || 'Saved.') + '</span>'
          : '<span class="err">' + escapeHtml(j.message || 'Save failed.') + '</span>';
      } finally {
        saveQr.disabled = false;
      }
    };
    actions.appendChild(openBtn); actions.appendChild(saveQr);
    wrap.appendChild(actions);
    resultsEl.appendChild(wrap);
    resultItems.push({ name: m.name, url: m.qrImg });
    if (resultItems.length >= 2) {
      saveAllBtn.style.display = '';
      regenAllBtn.style.display = '';
    }
  } else if (m.type === 'done') {
    statusEl.innerHTML = '<span class="ok">Done.</span>';
  }
}
</script>
`
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	_, _ = w.Write([]byte(Page("/applovin", "AppLovin Upload", body)))
}

func ApplovinPick(w http.ResponseWriter, r *http.Request) {
	cfg := LoadConfig()
	picked := PickFiles(
		"Select HTML files",
		[]FileFilter{{Name: "HTML", Extensions: []string{"html", "htm"}}},
		true, cfg.LastPickDir,
	)
	if len(picked) > 0 {
		cfg.LastPickDir = filepath.Dir(picked[0])
		_ = SaveConfig(cfg)
	}
	files := make([]map[string]string, 0, len(picked))
	for _, p := range picked {
		files = append(files, map[string]string{"path": p, "name": filepath.Base(p)})
	}
	writeJSON(w, map[string]any{"files": files})
}

func ApplovinUpload(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Paths []string `json:"paths"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	cfg := LoadConfig().Applovin
	cookie := cfg.Cookie
	delayMs := cfg.DelayMs
	if delayMs < 0 {
		delayMs = 0
	}
	randomizeUA := true
	if cfg.RandomizeUserAgent != nil {
		randomizeUA = *cfg.RandomizeUserAgent
	}

	w.Header().Set("Content-Type", "application/x-ndjson")
	flusher, _ := w.(http.Flusher)

	send := func(obj any) {
		data, _ := json.Marshal(obj)
		_, _ = w.Write(append(data, '\n'))
		if flusher != nil {
			flusher.Flush()
		}
	}

	htmlRx := regexp.MustCompile(`(?i)\.html?$`)

	buildForm := func(filePath string) (*bytes.Buffer, string, error) {
		data, err := os.ReadFile(filePath)
		if err != nil {
			return nil, "", err
		}
		var buf bytes.Buffer
		mw := multipart.NewWriter(&buf)
		fw, err := createFormFileWithType(mw, "file", filepath.Base(filePath), "text/html")
		if err != nil {
			return nil, "", err
		}
		if _, err := fw.Write(data); err != nil {
			return nil, "", err
		}
		if err := mw.Close(); err != nil {
			return nil, "", err
		}
		return &buf, mw.FormDataContentType(), nil
	}

	doPost := func(url string, body *bytes.Buffer, contentType string) (int, string, error) {
		req, err := http.NewRequest("POST", url, body)
		if err != nil {
			return 0, "", err
		}
		req.Header.Set("Content-Type", contentType)
		req.Header.Set("Accept", "application/json, text/javascript, */*; q=0.01")
		req.Header.Set("Origin", "https://p.applov.in")
		req.Header.Set("Referer", "https://p.applov.in/playablePreview?create=1&qr=1")
		req.Header.Set("X-Requested-With", "XMLHttpRequest")
		req.Header.Set("User-Agent", pickApplovinUserAgent(randomizeUA))
		if cookie != "" {
			req.Header.Set("Cookie", "__Host-APPLOVINID="+cookie)
		}
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			return 0, "", err
		}
		defer resp.Body.Close()
		out, _ := io.ReadAll(resp.Body)
		return resp.StatusCode, string(out), nil
	}

	for i, filePath := range req.Paths {
		fileName := filepath.Base(filePath)
		idx := i + 1
		fileErr := func(msg string) {
			send(map[string]any{"type": "fileError", "name": fileName, "message": msg})
		}

		if filePath == "" || !fileExists(filePath) {
			fileErr("File missing")
			continue
		}
		if !htmlRx.MatchString(filePath) {
			fileErr("Must be .html")
			continue
		}

		if i > 0 && delayMs > 0 {
			send(map[string]any{"type": "status", "message": fmt.Sprintf("[%d/%d] Waiting %dms before %s...", idx, len(req.Paths), delayMs, fileName)})
			time.Sleep(time.Duration(delayMs) * time.Millisecond)
		}

		send(map[string]any{"type": "status", "message": fmt.Sprintf("[%d/%d] Validating %s...", idx, len(req.Paths), fileName)})

		body, ct, err := buildForm(filePath)
		if err != nil {
			fileErr("Read failed: " + err.Error())
			continue
		}
		status, respText, err := doPost("https://p.applov.in/validateHTMLContent", body, ct)
		if err != nil {
			fileErr("Network (validate): " + err.Error())
			continue
		}
		if status < 200 || status >= 300 {
			fileErr(fmt.Sprintf("Validate HTTP %d: %s", status, truncate(respText, 300)))
			continue
		}
		var vJSON map[string]any
		_ = json.Unmarshal([]byte(respText), &vJSON)
		if code, ok := vJSON["code"]; ok {
			if codeNum, isNum := code.(float64); isNum && codeNum != 200 {
				fileErr("Validation failed: " + truncate(respText, 300))
				continue
			}
		}

		send(map[string]any{"type": "status", "message": fmt.Sprintf("[%d/%d] Uploading %s...", idx, len(req.Paths), fileName)})

		body, ct, err = buildForm(filePath)
		if err != nil {
			fileErr("Read failed: " + err.Error())
			continue
		}
		status, respText, err = doPost("https://p.applov.in/getCachedAdURL", body, ct)
		if err != nil {
			fileErr("Network (cache): " + err.Error())
			continue
		}
		if status < 200 || status >= 300 {
			fileErr(fmt.Sprintf("Cache HTTP %d: %s", status, truncate(respText, 300)))
			continue
		}
		var cJSON map[string]any
		if err := json.Unmarshal([]byte(respText), &cJSON); err != nil {
			fileErr("Non-JSON: " + truncate(respText, 300))
			continue
		}
		hash, _ := cJSON["results"].(string)
		if hash == "" {
			fileErr("No hash in response: " + truncate(respText, 300))
			continue
		}

		send(map[string]any{
			"type":       "fileResult",
			"name":       fileName,
			"hash":       hash,
			"previewUrl": "https://p.applov.in/getPreviewHTML?preview=" + hash,
			"pageUrl":    "https://p.applov.in/playablePreview?preview=" + hash + "&qr=1",
			"qrImg":      "https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=" + urlpkg.QueryEscape(hash),
		})
	}
	send(map[string]any{"type": "done"})
}

func qrBaseName(name string) string {
	base := name
	if base == "" {
		base = "qr"
	}
	base = regexp.MustCompile(`(?i)\.html?$`).ReplaceAllString(base, "")
	base = regexp.MustCompile(`[\\/:*?"<>|]`).ReplaceAllString(base, "_")
	if base == "" {
		base = "qr"
	}
	return base
}

func downloadQR(url, destPath string) error {
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("HTTP %d", resp.StatusCode)
	}
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	return os.WriteFile(destPath, data, 0644)
}

func ApplovinSaveQR(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Name string `json:"name"`
		URL  string `json:"url"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, map[string]any{"ok": false, "message": err.Error()})
		return
	}
	if req.URL == "" {
		writeJSON(w, map[string]any{"ok": false, "message": "Missing URL"})
		return
	}
	base := qrBaseName(req.Name)
	dest := PickSaveFile("Save QR", base+".png", []FileFilter{{Name: "PNG Image", Extensions: []string{"png"}}}, "")
	if dest == "" {
		writeJSON(w, map[string]any{"ok": false, "message": "Cancelled"})
		return
	}
	if err := downloadQR(req.URL, dest); err != nil {
		writeJSON(w, map[string]any{"ok": false, "message": "Save failed: " + err.Error()})
		return
	}
	writeJSON(w, map[string]any{"ok": true, "message": "Saved QR: " + dest})
}

func ApplovinSaveAllQRs(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Items []struct {
			Name string `json:"name"`
			URL  string `json:"url"`
		} `json:"items"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeJSON(w, map[string]any{"ok": false, "message": err.Error()})
		return
	}
	if len(req.Items) == 0 {
		writeJSON(w, map[string]any{"ok": false, "message": "No items"})
		return
	}
	dir := PickFolder("Choose folder to save QRs", "")
	if dir == "" {
		writeJSON(w, map[string]any{"ok": false, "message": "Cancelled"})
		return
	}
	saved := 0
	errs := []string{}
	for _, it := range req.Items {
		base := qrBaseName(it.Name)
		dest := filepath.Join(dir, base+".png")
		n := 1
		for fileExists(dest) {
			dest = filepath.Join(dir, fmt.Sprintf("%s (%d).png", base, n))
			n++
		}
		if err := downloadQR(it.URL, dest); err != nil {
			errs = append(errs, it.Name+": "+err.Error())
			continue
		}
		saved++
	}
	if len(errs) > 0 {
		writeJSON(w, map[string]any{
			"ok":      false,
			"message": fmt.Sprintf("Saved %d/%d. Errors: %s", saved, len(req.Items), strings.Join(errs, "; ")),
		})
		return
	}
	writeJSON(w, map[string]any{
		"ok":      true,
		"message": fmt.Sprintf("Saved %d QR%s to %s", saved, plural(saved), dir),
	})
}

func plural(n int) string {
	if n == 1 {
		return ""
	}
	return "s"
}
