// fikoyt-eklenti-everything (SDK surumu, Asama 4.1 on calisma).
// Canli everything eklentisi degistirilmez; bu surum dogrulandiktan sonra gecis yapilir.
// Islemler: ara, dosya-parca, klasor-ac. Ping ve merhaba SDK tarafindan yanitlanir.
// Kucuk resim, simge ve CATIA onizlemesi ayri dosyalarda (kucuk_resim.go, kucuk_simge.go, catgomulu_onizleme.go).
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"

	sdk "fikoyt-eklenti-sdk"
)

const (
	maksSorgu     = 400
	maksSonuc     = 200
	maksYanitBoyu = 512 * 1024
)

// hostDesen: yalnizca ana bilgisayar adi ya da IPv4 (sema, yol ya da port icermez).
var hostDesen = regexp.MustCompile(`^[A-Za-z0-9.-]{1,253}$`)

// windowsYolDesen: yalnizca mutlak Windows yolu. Goreli yol ve UNC kabul edilmez.
var windowsYolDesen = regexp.MustCompile(`^[A-Za-z]:\\[^<>"|?*\x00-\x1f]+$`)

// siralamaAlanlari: web'den gelen siralama alanlarinin Everything sort adlari.
var siralamaAlanlari = map[string]string{
	"ad":    "name",
	"konum": "path",
	"tur":   "name",
	"boyut": "size",
	"tarih": "date_modified",
}

var istemci = &http.Client{Timeout: 10 * time.Second}

// ayarTanim: fikoyt/ayar ile gelen, bu PC'de kullanicinin kaydettigi Everything baglanti ayari.
type ayarTanim struct {
	Port      int    `json:"port"`
	Host      string `json:"host"`
	Kullanici string `json:"kullanici"`
	Parola    string `json:"parola"`
	IndirmeSuresiDk int `json:"indirmeSuresiDk"`
	IndirmeMaksMb   int `json:"indirmeMaksMb"`
}

// aramaVeri: ara islemi icin istek verisi.
type aramaVeri struct {
	Sorgu     string `json:"sorgu"`
	Sayfa     int    `json:"sayfa"`
	Boyut     int    `json:"boyut"`
	Siralama  string `json:"siralama"`
	Artan     bool   `json:"artan"`
	Case      bool   `json:"case"`
	WholeWord bool   `json:"wholeword"`
	FullPath  bool   `json:"path"`
	Regex     bool   `json:"regex"`
}

// klasorVeri: klasor-ac islemi icin istek verisi.
type klasorVeri struct {
	Yol string `json:"yol"`
}

var (
	errSorgu      = errors.New("sorgu gerekli ve en fazla 400 karakter")
	errUlasilamaz = errors.New("Everything HTTP sunucusuna ulasilamadi")
	errKimlik     = errors.New("Everything kullanici adi veya parola hatali")
)

// adresOlustur: ayardan Everything HTTP adresini kurar. Gecersizse hata doner.
func adresOlustur(a ayarTanim) (string, error) {
	if a.Port != 0 && (a.Port < 1 || a.Port > 65535) {
		return "", errors.New("gecersiz port")
	}
	host := a.Host
	if host == "" {
		host = "127.0.0.1"
	}
	if !hostDesen.MatchString(host) {
		return "", errors.New("gecersiz sunucu adresi")
	}
	port := a.Port
	if port == 0 {
		port = 8888
	}
	return fmt.Sprintf("http://%s:%d/", host, port), nil
}

// ara: Everything HTTP sunucusunda sayfalanmis arama yapar. Ayar istek basina verilir; global durum yoktur.
func ara(a ayarTanim, v aramaVeri) (json.RawMessage, error) {
	adres, err := adresOlustur(a)
	if err != nil {
		return nil, err
	}
	boyut := v.Boyut
	if boyut < 1 || boyut > maksSonuc {
		boyut = 50
	}
	sayfa := v.Sayfa
	if sayfa < 1 || sayfa > 100000 {
		sayfa = 1
	}
	hedef, err := url.Parse(adres)
	if err != nil {
		return nil, err
	}
	q := url.Values{}
	q.Set("search", v.Sorgu)
	q.Set("json", "1")
	q.Set("count", strconv.Itoa(boyut))
	q.Set("offset", strconv.Itoa((sayfa-1)*boyut))
	q.Set("path_column", "1")
	q.Set("size_column", "1")
	q.Set("date_modified_column", "1")
	if v.Case {
		q.Set("case", "1")
	}
	if v.WholeWord {
		q.Set("wholeword", "1")
	}
	if v.FullPath {
		q.Set("path", "1")
	}
	if v.Regex {
		q.Set("regex", "1")
	}
	if alan, ok := siralamaAlanlari[v.Siralama]; ok {
		q.Set("sort", alan)
		if v.Artan {
			q.Set("ascending", "1")
		} else {
			q.Set("ascending", "0")
		}
	}
	hedef.RawQuery = q.Encode()

	istek, err := http.NewRequest(http.MethodGet, hedef.String(), nil)
	if err != nil {
		return nil, errUlasilamaz
	}
	if a.Kullanici != "" || a.Parola != "" {
		istek.SetBasicAuth(a.Kullanici, a.Parola)
	}
	yanit, err := istemci.Do(istek)
	if err != nil {
		return nil, errUlasilamaz
	}
	defer yanit.Body.Close()
	if yanit.StatusCode == http.StatusUnauthorized {
		return nil, errKimlik
	}
	if yanit.StatusCode != http.StatusOK {
		return nil, errUlasilamaz
	}
	govde, err := io.ReadAll(io.LimitReader(yanit.Body, maksYanitBoyu))
	if err != nil {
		return nil, errUlasilamaz
	}
	if !json.Valid(govde) {
		return nil, errors.New("Everything gecersiz yanit dondu")
	}
	return govde, nil
}

// klasorAc: dosyanin bulundugu klasoru Gezgin'de acar ve dosyayi secili gosterir. Dosya calistirilmaz.
func klasorAc(yol string) error {
	yol = strings.TrimSpace(yol)
	if len(yol) > 1024 || !windowsYolDesen.MatchString(yol) || strings.Contains(yol, "..") {
		return errors.New("gecersiz dosya yolu")
	}
	bilgi, err := os.Stat(yol)
	if err != nil {
		return errors.New("dosya bulunamadi")
	}
	// Klasor de kabul edilir: explorer /select, klasoru ust klasorunde secili gosterir.
	_ = bilgi
	if err := explorerAc(yol); err != nil {
		return errors.New("klasor acilamadi")
	}
	return nil
}

// ayarOku: fikoyt/ayar ile gelen ham JSON'u ayarTanim'e cevirir. Bos ise varsayilan ayar kullanilir.
func ayarOku(ham json.RawMessage) ayarTanim {
	var a ayarTanim
	if len(ham) > 0 {
		_ = json.Unmarshal(ham, &a)
	}
	return a
}

// onizlemeZenginlestir: kullanicinin ayarina gore sonuca kucuk onizleme ve dosya simgesi alanlari ekler.
func onizlemeZenginlestir(veri json.RawMessage, ayar json.RawMessage) json.RawMessage {
	if kucuk := kucukAyarOku(ayar); kucuk.Acik {
		veri = kucukOnizlemeleriEkle(veri, kucuk)
	}
	if simgeAcik(ayar) {
		veri = simgeleriEkle(veri)
	}
	return veri
}

func main() {
	e := sdk.Yeni("everything").Havuz(4)

	e.Kayit("ara", func(c *sdk.Cagri) (any, error) {
		var v aramaVeri
		if err := json.Unmarshal(c.Veri, &v); err != nil {
			return nil, errors.New("gecersiz veri")
		}
		v.Sorgu = strings.TrimSpace(v.Sorgu)
		if v.Sorgu == "" || len(v.Sorgu) > maksSorgu {
			return nil, errSorgu
		}
		a := ayarOku(c.Ayar)
		veri, err := ara(a, v)
		if err != nil {
			return nil, err
		}
		izinKaydet(veri, a.IndirmeSuresiDk)
		return onizlemeZenginlestir(veri, c.Ayar), nil
	})

	e.Kayit("dosya-parca", func(c *sdk.Cagri) (any, error) {
		var v struct {
			Yol       string `json:"yol"`
			Baslangic int64  `json:"baslangic"`
			Uzunluk   int64  `json:"uzunluk"`
		}
		if err := json.Unmarshal(c.Veri, &v); err != nil {
			return nil, errors.New("gecersiz veri")
		}
		return dosyaParca(v.Yol, v.Baslangic, v.Uzunluk, ayarOku(c.Ayar).IndirmeMaksMb)
	})

	e.Kayit("klasor-ac", func(c *sdk.Cagri) (any, error) {
		var v klasorVeri
		if err := json.Unmarshal(c.Veri, &v); err != nil {
			return nil, errors.New("gecersiz veri")
		}
		if err := klasorAc(v.Yol); err != nil {
			return nil, err
		}
		return map[string]bool{"tamam": true}, nil
	})

	// Girdi kapaninca (bridge sureci durdurunca) Calistir doner; surec temiz biter.
	_ = e.Calistir(os.Stdin, os.Stdout)
}
