package main

// Cihaz kimligi ve senkron.
//   - Her profil icin rastgele bir cihaz kimligi uretilir (profilin klasorunde saklanir).
//   - Giris yapildiginda cihaz, kullanicinin hesabina kaydedilir (bridge.cihaz.kaydet).
//   - Periyodik olarak senkron yapilir:
//       * Web'den kapatilan izinler yerelde de kapatilir (onay gerektirmez).
//       * Web'den acilma istegi (bekleyen) gelirse YEREL onay ekrani acilir;
//         karar sunucuya bildirilir (bridge.cihaz.karar).
//   - Yerel onay olmadan hicbir izin acilmaz.

import (
	"bytes"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

const fikoytCihazApi = "https://fikoyt.com:9000/fapiv1/fikoyt.com.FikoYT.sistem.bridge.cihaz."

// cihazMakineDosyasi: kimligin hangi bilgisayara ait oldugunu tutar (bilgisayar adinin ozeti).
const cihazMakineDosyasi = "cihaz-makine"

func makineParmagi() string {
	ad, err := os.Hostname()
	if err != nil || ad == "" {
		ad = "bilinmeyen"
	}
	ozet := sha256.Sum256([]byte(strings.ToLower(ad)))
	return hex.EncodeToString(ozet[:])
}

// cihazMu: kimlik/gizli uretimi eszamanli cagrilabilir (senkron dongusu + socket ayni anda
// yeni bir profili ilk kez gorebilir). Kilitsiz uretimde ayni profil icin iki farkli kimlik
// uretilip sunucuya iki cihaz kaydi gidebilir (yasandi: _mer, 2026-10-16).
var cihazMu sync.Mutex

// cihazKimlikleriBuBilgisayaraProfil: verilen profilin kimligi bu bilgisayara ait degilse
// (ya da hic kaydedilmemisse) kimlik ve gizli anahtari yeniden uretir. Baska bir bilgisayardan
// kopyalanan profil, bu adimda kendi kimligini alir.
func cihazKimlikleriBuBilgisayaraProfil(anahtar string) error {
	return cihazKimlikleriKilitli(anahtar)
}

// cihazKimlikleriKilitli: kilitsiz govde. cihazMu kilitliyken cagrilmali.
func cihazKimlikleriKilitli(anahtar string) error {
	klasor, err := profilKlasorP(anahtar)
	if err != nil {
		return err
	}
	parmak := makineParmagi()
	mYol := filepath.Join(klasor, cihazMakineDosyasi)
	if veri, err := os.ReadFile(mYol); err == nil && string(veri) == parmak {
		return nil
	}
	for _, ad := range []string{"cihaz-kimlik", "cihaz-gizli"} {
		if err := os.Remove(filepath.Join(klasor, ad)); err != nil && !errors.Is(err, os.ErrNotExist) {
			return err
		}
	}
	olayLog("cihaz kimliği bu bilgisayara özel olarak yenilendi (%s)", anahtar)
	return os.WriteFile(mYol, []byte(parmak), 0o600)
}

// cihazKimligiProfil: verilen profilin cihaz kimligi. Yoksa uretir, varsa okur.
// Tum zincir (makine baglama + kimlik oku/uret + gizli oku/uret) ayni kilit altinda
// calisir; eszamanli cagrilarda tek kimlik uretilir.
func cihazKimligiProfil(anahtar string) (string, error) {
	cihazMu.Lock()
	defer cihazMu.Unlock()
	return cihazKimligiKilitli(anahtar)
}

// cihazKimligiKilitli: kilitsiz govde. cihazMu kilitliyken cagrilmali.
func cihazKimligiKilitli(anahtar string) (string, error) {
	if err := cihazKimlikleriKilitli(anahtar); err != nil {
		return "", err
	}
	klasor, err := profilKlasorP(anahtar)
	if err != nil {
		return "", err
	}
	yol := filepath.Join(klasor, "cihaz-kimlik")
	if veri, err := os.ReadFile(yol); err == nil && len(veri) >= 16 {
		return string(veri), nil
	}
	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	kimlik := hex.EncodeToString(b)
	return kimlik, os.WriteFile(yol, []byte(kimlik), 0o600)
}

// fikoytCagriProfil: verilen profilin oturum cerezleriyle fikoyt.com cihaz ucuna istek atar.
// Sunucu cevabinda durum.hata true ise hata dondurur, degilse "sonuc" alanini dondurur.
func fikoytCagriProfil(anahtar, uc string, veri map[string]any) (map[string]any, error) {
	reqBody, err := json.Marshal(veri)
	if err != nil {
		return nil, err
	}
	var govde bytes.Buffer
	mw := multipart.NewWriter(&govde)
	if err := mw.WriteField("req_body", string(reqBody)); err != nil {
		return nil, err
	}
	mw.Close()

	istek, err := http.NewRequest(http.MethodPost, fikoytCihazApi+uc, &govde)
	if err != nil {
		return nil, err
	}
	istek.Header.Set("Content-Type", mw.FormDataContentType())
	istek.Header.Set("Accept", "application/json")
	if err := fikoytIstekCerezleriProfil(istek, anahtar); err != nil {
		return nil, errors.New("giriş gerekli")
	}

	istemci := &http.Client{Timeout: 15 * time.Second}
	yanit, err := istemci.Do(istek)
	if err != nil {
		return nil, errors.New("sunucuya ulaşılamadı")
	}
	defer yanit.Body.Close()
	icerik, _ := io.ReadAll(io.LimitReader(yanit.Body, 1<<20))

	var cevap map[string]any
	if json.Unmarshal(icerik, &cevap) != nil {
		return nil, fmt.Errorf("sunucu HTTP %d döndü", yanit.StatusCode)
	}
	if durum, ok := cevap["durum"].(map[string]any); ok {
		if hata, _ := durum["hata"].(bool); hata {
			mesaj, _ := durum["mesaj"].(string)
			if mesaj == "" {
				mesaj = fmt.Sprintf("sunucu hatası (HTTP %d)", yanit.StatusCode)
			}
			return nil, errors.New(mesaj)
		}
	}
	if yanit.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("sunucu HTTP %d döndü", yanit.StatusCode)
	}
	sonuc, _ := cevap["sonuc"].(map[string]any)
	if sonuc == nil {
		sonuc = map[string]any{}
	}
	return sonuc, nil
}

// fikoytCagri: aktif profil uzerinden fikoyt.com cihaz ucu cagrisi.

// cihazKaydetProfil: cihazi verilen profilin hesabina kaydeder.
func cihazKaydetProfil(anahtar, kimlik string) error {
	ad, err := os.Hostname()
	if err != nil || ad == "" {
		ad = "Bilgisayar"
	}
	gizliOzet, err := cihazGizliOzetProfil(anahtar)
	if err != nil {
		return err
	}
	_, err = fikoytCagriProfil(anahtar, "kaydet", map[string]any{"cihazKimlik": kimlik, "ad": ad, "gizliOzet": gizliOzet})
	return err
}

// cihazSenkronDongusu: etkin her hesap icin senkron dongusu. Sunucu socket'i profil basina
// profilSoketDongusu tarafindan yonetilir; bu dongu yalnizca senkron ve kayit islerini yapar.
func cihazSenkronDongusu() {
	kayitli := map[string]bool{}
	for {
		select {
		case <-senkronTetik: // sunucu degisiklik bildirdi (socket) ya da profil degisti
		case <-time.After(10 * time.Second): // yedek periyodik senkron
		}
		for _, profil := range etkinHesaplar() {
			if err := cihazSenkronProfil(profil, kayitli); err != nil {
				olayLog("senkron [%s]: %v", profil, err)
			}
		}
	}
}

// cihazSenkronProfil: tek profil icin oturum kontrolu, kayit ve senkron. kayitli: profil -> kayitli mi.
func cihazSenkronProfil(profil string, kayitli map[string]bool) error {
	if _, err := oturumOkuProfil(profil); err != nil {
		kayitli[profil] = false
		return nil
	}
	kimlik, err := cihazKimligiProfil(profil)
	if err != nil {
		return fmt.Errorf("cihaz kimligi okunamadi: %w", err)
	}
	if !kayitli[profil] {
		if err := cihazKaydetProfil(profil, kimlik); err != nil {
			return fmt.Errorf("cihaz kaydi: %w", err)
		}
		kayitli[profil] = true
		olayLog("cihaz hesaba kaydedildi [%s]", profil)
	}
	if err := senkronBirProfil(profil, kimlik); err != nil {
		// Sunucuda kayit yoksa (or. eslesme web'den kaldirildi) bir sonraki turda yeniden kaydol.
		if strings.Contains(err.Error(), "bulunamad") {
			kayitli[profil] = false
		}
		return err
	}
	return nil
}

func senkronBirProfil(profil, kimlik string) error {
	sonuc, err := fikoytCagriProfil(profil, "senkron", map[string]any{"cihazKimlik": kimlik})
	if err != nil {
		return err
	}

	// 1) Web'den kapatilan izinleri yerelde de kapat.
	sunucuIzin, _ := sonuc["izinler"].(map[string]any)
	yerel := izinOkuProfil(profil)
	degisti := false
	if !sunucuAcik(sunucuIzin, "everythingAra") && yerel.EverythingAra {
		yerel.EverythingAra = false
		degisti = true
	}
	if !sunucuAcik(sunucuIzin, "catiaAc") && yerel.CatiaAc {
		yerel.CatiaAc = false
		degisti = true
	}
	if !sunucuAcik(sunucuIzin, "usb") && yerel.UsbBaglanti {
		yerel.UsbBaglanti = false
		degisti = true
	}
	if !sunucuAcik(sunucuIzin, "dosyaYonetimi") && yerel.Acik("dosyaYonetimi") {
		yerel.DosyaYonetimiBitis = 0
		degisti = true
	}
	if degisti {
		if err := izinleriKaydetProfil(profil, yerel); err != nil {
			return err
		}
		olayLog("web'den kapatılan izinler yerelde uygulandı [%s]", profil)
		izinDurumGonderProfil(profil)
	}

	// 2) Bekleyen acma istegi varsa YEREL onay iste.
	bekleyen, _ := sonuc["bekleyen"].(map[string]any)
	if bekleyen == nil {
		return nil
	}
	anahtar, _ := bekleyen["anahtar"].(string)
	etiket, ok := izinEtiketleri[anahtar]
	if !ok {
		_, _ = fikoytCagriProfil(profil, "karar", map[string]any{"cihazKimlik": kimlik, "anahtar": anahtar, "izin": false})
		return nil
	}
	if izinOkuProfil(profil).Acik(anahtar) {
		// Zaten acik; istegi kapat.
		_, err := fikoytCagriProfil(profil, "karar", map[string]any{"cihazKimlik": kimlik, "anahtar": anahtar, "izin": true})
		return err
	}

	onay, sure, err := izinOnayla(anahtar, hesapBaslik(profil, etiket.baslik), etiket.aciklama)
	if errors.Is(err, errMesgul) {
		return nil // baska bir onay acik; sonraki turda tekrar denenir
	}
	if err != nil {
		return err
	}
	if onay {
		onay, err = izinKaydetOnayProfil(profil, anahtar, sure)
		if err != nil {
			return err
		}
	}
	_, err = fikoytCagriProfil(profil, "karar", map[string]any{"cihazKimlik": kimlik, "anahtar": anahtar, "izin": onay})
	return err
}

func sunucuAcik(m map[string]any, anahtar string) bool {
	b, _ := m[anahtar].(bool)
	return b
}

// senkronTetik: socket'ten gelen degisiklik bildirimi icin (tampon 1; fazlasi birlesir).
var senkronTetik = make(chan struct{}, 1)

func senkronTetikle() {
	select {
	case senkronTetik <- struct{}{}:
	default:
	}
}

// cihazGizliOkuProfil: verilen profilin cihaz gizli anahtari (yoksa uretir). Sadece bu
// bilgisayarda saklanir; sunucuya yalnizca SHA-256 ozeti gonderilir.
func cihazGizliOkuProfil(anahtar string) (string, error) {
	cihazMu.Lock()
	defer cihazMu.Unlock()
	return cihazGizliOkuKilitli(anahtar)
}

// cihazGizliOkuKilitli: kilitsiz govde. cihazMu kilitliyken cagrilmali.
func cihazGizliOkuKilitli(anahtar string) (string, error) {
	if err := cihazKimlikleriKilitli(anahtar); err != nil {
		return "", err
	}
	klasor, err := profilKlasorP(anahtar)
	if err != nil {
		return "", err
	}
	yol := filepath.Join(klasor, "cihaz-gizli")
	if veri, err := os.ReadFile(yol); err == nil && len(veri) >= 32 {
		return string(veri), nil
	}
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	gizli := hex.EncodeToString(b)
	return gizli, os.WriteFile(yol, []byte(gizli), 0o600)
}

// cihazGizliOzetProfil: verilen profilin gizli anahtarinin SHA-256 ozeti.
func cihazGizliOzetProfil(anahtar string) (string, error) {
	gizli, err := cihazGizliOkuProfil(anahtar)
	if err != nil {
		return "", err
	}
	ozet := sha256.Sum256([]byte(gizli))
	return hex.EncodeToString(ozet[:]), nil
}
