// dosya-yoneticisi: bridge uzerinden web'e dosya listeleme, okuma, yazma, kopyalama ve silme saglar.
// Protokol: stdin'e tek satir JSON {islem, veri}; stdout'a tek satir JSON {tamam, veri, hata}.
// Izin kontrolu bridge'de yapilir; bu eklenti yalnizca gelen istegi dogrular ve calistirir.
// Yazma ve kopyalama once gecici dosyaya yazilir, sonra yeniden adlandirilir (yarim dosya kalmaz).
package main

import (
	"bufio"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sort"
)

// Bir parcanin ham boyutu. Base64 sonrasi ~683 KB olur; bridge cikti siniri 1 MB.
const maksParcaBoyutu = 512 * 1024

// Tek listelemede donen en fazla kayit sayisi.
const maksListeKaydi = 2000

// Gecici dosya eki. Yazma ve kopyalama bu adla yapilir, bitince hedefe tasinir.
const geciciEk = ".fikoyt-gecici"

type girdi struct {
	Islem string          `json:"islem"`
	Veri  json.RawMessage `json:"veri"`
}

type cikti struct {
	Tamam bool   `json:"tamam"`
	Veri  any    `json:"veri,omitempty"`
	Hata  string `json:"hata,omitempty"`
}

// veriIslem: tum islemlerin veri alanlari (her islem kendi alanlarini kullanir).
type veriIslem struct {
	Yol        string `json:"yol"`
	Ofset      int64  `json:"ofset"`
	Uzunluk    int64  `json:"uzunluk"`
	Kaynak     string `json:"kaynak"`
	Hedef      string `json:"hedef"`
	Icerik     string `json:"icerik"`
	Ilk        bool   `json:"ilk"`
	Bitti      bool   `json:"bitti"`
	UzerineYaz bool   `json:"uzerineYaz"`
}

type kayit struct {
	Ad      string `json:"ad"`
	Tip     string `json:"tip"`
	Boyut   int64  `json:"boyut"`
	Degisim int64  `json:"degisim"`
}

func main() {
	satir, err := bufio.NewReader(os.Stdin).ReadBytes('\n')
	if err != nil && len(satir) == 0 {
		yaz(cikti{Hata: "girdi okunamadi"})
		return
	}
	var g girdi
	if err := json.Unmarshal(satir, &g); err != nil {
		yaz(cikti{Hata: "gecersiz girdi"})
		return
	}
	var v veriIslem
	if err := json.Unmarshal(g.Veri, &v); err != nil {
		yaz(cikti{Hata: "gecersiz veri"})
		return
	}
	switch g.Islem {
	case "listele":
		yaz(listele(v.Yol))
	case "oku":
		yaz(oku(v))
	case "yaz":
		yaz(yaz2(v))
	case "kopyala":
		yaz(kopyala(v))
	case "sil":
		yaz(sil(v))
	default:
		yaz(cikti{Hata: "bu islem desteklenmiyor"})
	}
}

func yaz(c cikti) {
	_ = json.NewEncoder(os.Stdout).Encode(c)
}

// yolKontrol: yalnizca mutlak ve temizlenmis yollar kabul edilir.
func yolKontrol(yol string) (string, error) {
	if yol == "" {
		return "", fmt.Errorf("yol bos")
	}
	if !filepath.IsAbs(yol) {
		return "", fmt.Errorf("yol mutlak olmali")
	}
	return filepath.Clean(yol), nil
}

func listele(yol string) cikti {
	t, err := yolKontrol(yol)
	if err != nil {
		return cikti{Hata: err.Error()}
	}
	girdiler, err := os.ReadDir(t)
	if err != nil {
		return cikti{Hata: "klasor okunamadi"}
	}
	liste := []kayit{}
	for _, g := range girdiler {
		if len(liste) >= maksListeKaydi {
			break
		}
		bilgi, err := g.Info()
		if err != nil {
			continue
		}
		tip := "dosya"
		if g.IsDir() {
			tip = "klasor"
		}
		liste = append(liste, kayit{Ad: g.Name(), Tip: tip, Boyut: bilgi.Size(), Degisim: bilgi.ModTime().Unix()})
	}
	sort.Slice(liste, func(i, j int) bool {
		if liste[i].Tip != liste[j].Tip {
			return liste[i].Tip == "klasor"
		}
		return liste[i].Ad < liste[j].Ad
	})
	return cikti{Tamam: true, Veri: map[string]any{"yol": t, "kayitlar": liste, "toplam": len(girdiler)}}
}

func oku(v veriIslem) cikti {
	t, err := yolKontrol(v.Yol)
	if err != nil {
		return cikti{Hata: err.Error()}
	}
	f, err := os.Open(t)
	if err != nil {
		return cikti{Hata: "dosya acilamadi"}
	}
	defer f.Close()
	bilgi, err := f.Stat()
	if err != nil || bilgi.IsDir() {
		return cikti{Hata: "dosya degil"}
	}
	ofset := v.Ofset
	if ofset < 0 {
		ofset = 0
	}
	if ofset > bilgi.Size() {
		ofset = bilgi.Size()
	}
	uzunluk := v.Uzunluk
	if uzunluk <= 0 || uzunluk > maksParcaBoyutu {
		uzunluk = maksParcaBoyutu
	}
	if _, err := f.Seek(ofset, io.SeekStart); err != nil {
		return cikti{Hata: "okunamadi"}
	}
	tampon := make([]byte, uzunluk)
	n, err := io.ReadFull(f, tampon)
	if err != nil && err != io.ErrUnexpectedEOF && err != io.EOF {
		return cikti{Hata: "okunamadi"}
	}
	tampon = tampon[:n]
	return cikti{Tamam: true, Veri: map[string]any{
		"yol":     t,
		"boyut":   bilgi.Size(),
		"ofset":   ofset,
		"uzunluk": n,
		"bitti":   ofset+int64(n) >= bilgi.Size(),
		"icerik":  base64.StdEncoding.EncodeToString(tampon),
	}}
}

// yaz2: parca parca yazma. Ilk parca gecici dosyayi olusturur, son parca hedefe tasir.
func yaz2(v veriIslem) cikti {
	t, err := yolKontrol(v.Yol)
	if err != nil {
		return cikti{Hata: err.Error()}
	}
	parca, err := base64.StdEncoding.DecodeString(v.Icerik)
	if err != nil || len(parca) > maksParcaBoyutu {
		return cikti{Hata: "gecersiz parca"}
	}
	gecici := t + geciciEk
	if v.Ilk {
		if _, err := os.Stat(t); err == nil && !v.UzerineYaz {
			return cikti{Hata: "dosya zaten var"}
		}
		if err := os.WriteFile(gecici, nil, 0o644); err != nil {
			return cikti{Hata: "dosya olusturulamadi"}
		}
	}
	f, err := os.OpenFile(gecici, os.O_APPEND|os.O_WRONLY, 0)
	if err != nil {
		return cikti{Hata: "yazma oturumu bulunamadi"}
	}
	if _, err := f.Write(parca); err != nil {
		f.Close()
		os.Remove(gecici)
		return cikti{Hata: "yazılamadı"}
	}
	if err := f.Close(); err != nil {
		os.Remove(gecici)
		return cikti{Hata: "yazılamadı"}
	}
	if !v.Bitti {
		return cikti{Tamam: true, Veri: map[string]any{"yol": t, "bitti": false}}
	}
	if _, err := os.Stat(t); err == nil && !v.UzerineYaz {
		os.Remove(gecici)
		return cikti{Hata: "dosya zaten var"}
	}
	if err := os.Rename(gecici, t); err != nil {
		os.Remove(gecici)
		return cikti{Hata: "dosya kaydedilemedi"}
	}
	bilgi, _ := os.Stat(t)
	return cikti{Tamam: true, Veri: map[string]any{"yol": t, "bitti": true, "boyut": bilgi.Size()}}
}

// kopyala: dosyayi hedefe kopyalar. Once gecici dosyaya yazilir, sonra adlandirilir.
func kopyala(v veriIslem) cikti {
	kaynak, err := yolKontrol(v.Kaynak)
	if err != nil {
		return cikti{Hata: "kaynak: " + err.Error()}
	}
	hedef, err := yolKontrol(v.Hedef)
	if err != nil {
		return cikti{Hata: "hedef: " + err.Error()}
	}
	kb, err := os.Stat(kaynak)
	if err != nil || kb.IsDir() {
		return cikti{Hata: "kaynak dosya degil"}
	}
	if _, err := os.Stat(hedef); err == nil && !v.UzerineYaz {
		return cikti{Hata: "hedef dosya zaten var"}
	}
	src, err := os.Open(kaynak)
	if err != nil {
		return cikti{Hata: "kaynak acilamadi"}
	}
	defer src.Close()
	gecici := hedef + geciciEk
	dst, err := os.Create(gecici)
	if err != nil {
		return cikti{Hata: "hedef olusturulamadi"}
	}
	if _, err := io.Copy(dst, src); err != nil {
		dst.Close()
		os.Remove(gecici)
		return cikti{Hata: "kopyalanamadi"}
	}
	if err := dst.Close(); err != nil {
		os.Remove(gecici)
		return cikti{Hata: "kopyalanamadi"}
	}
	if err := os.Rename(gecici, hedef); err != nil {
		os.Remove(gecici)
		return cikti{Hata: "hedef kaydedilemedi"}
	}
	return cikti{Tamam: true, Veri: map[string]any{"kaynak": kaynak, "hedef": hedef, "boyut": kb.Size()}}
}

// sil: dosya veya klasor siler (klasor tum icerigiyle). Surucu koku asla silinmez.
func sil(v veriIslem) cikti {
	t, err := yolKontrol(v.Yol)
	if err != nil {
		return cikti{Hata: err.Error()}
	}
	bilgi, err := os.Stat(t)
	if err != nil {
		return cikti{Hata: "dosya bulunamadi"}
	}
	if bilgi.IsDir() {
		// Surucu koku (orn. D:\) hicbir durumda silinmez.
		if t == filepath.VolumeName(t)+string(filepath.Separator) || len(t) <= 3 {
			return cikti{Hata: "sürücü kökü silinemez"}
		}
		if err := os.RemoveAll(t); err != nil {
			return cikti{Hata: "klasör silinemedi"}
		}
		return cikti{Tamam: true, Veri: map[string]any{"yol": t, "klasor": true}}
	}
	if err := os.Remove(t); err != nil {
		return cikti{Hata: "silinemedi"}
	}
	return cikti{Tamam: true, Veri: map[string]any{"yol": t}}
}
