package pasien

import (
	"bytes"
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

const trackerFile = "last_migrated_patient_id.txt"

const (
	defaultBaseURL  = "https://main-api.demo-simrs-v4-rssa.multy.chat"
	defaultUsername = "adm_pendaftaran"
	defaultPassword = "1234"
)

// Config menyimpan konfigurasi untuk proses migrasi
type Config struct {
	BaseURL  string
	Username string
	Password string
	DB       *sql.DB // Menggunakan koneksi raw DB dari package database bawaan anda
}

// NewDefaultConfig membuat konfigurasi dengan kredensial default API
func NewDefaultConfig(db *sql.DB) Config {
	return Config{
		BaseURL:  defaultBaseURL,
		Username: defaultUsername,
		Password: defaultPassword,
		DB:       db,
	}
}

// PatientDB representasi struktur tabel public.m_pasien
type PatientDB struct {
	ID                 int            `db:"id"`
	NoMR               sql.NullString `db:"nomr"`
	Title              sql.NullString `db:"title"`
	Nama               sql.NullString `db:"nama"`
	TempatLahir        sql.NullString `db:"tempat"`
	TglLahir           sql.NullTime   `db:"tgllahir"`
	JenisKelamin       sql.NullString `db:"jeniskelamin"`
	NoKTP              sql.NullString `db:"noktp"`
	NoKTPBaru          sql.NullString `db:"noktp_baru"`
	NoKartu            sql.NullString `db:"no_kartu"`
	Pekerjaan          sql.NullString `db:"pekerjaan"`
	NoHP               sql.NullString `db:"no_hp"`
	Disabilitas        sql.NullString `db:"disabilitas"`
	HambatanKomunikasi sql.NullString `db:"hambatan_komunikasi"`
	TxtAgama           sql.NullString `db:"txt_agama"`
	TxtPendidikan      sql.NullString `db:"txt_pendidikan"`
	TglDaftar          sql.NullTime   `db:"tgldaftar"`
}

// ApiPayload merepresentasikan struktur JSON untuk API pengiriman pasien
type ApiPayload struct {
	Person         Person          `json:"person"`
	PersonContacts []PersonContact `json:"personContacts"`
	RegisteredAt   string          `json:"registeredAt"`
	NewBornStatus  bool            `json:"newBornStatus"`
	StatusCode     string          `json:"status_code"`
}

type Person struct {
	Name                     string `json:"name"`
	FrontTitle               string `json:"frontTitle"`
	EndTitle                 string `json:"endTitle"`
	BirthDate                string `json:"birthDate"`
	BirthPlace               string `json:"birthPlace"`
	BirthRegencyCode         string `json:"birthRegency_code,omitempty"`
	GenderCode               string `json:"gender_code"`
	ResidentIdentityNumber   string `json:"residentIdentityNumber"`
	ReligionCode             string `json:"religion_code"`
	EducationCode            string `json:"education_code"`
	OccupationCode           string `json:"occupation_code,omitempty"`
	OccupationName           string `json:"occupation_name"`
	Nationality              string `json:"nationality"`
	CommunicationIssueStatus bool   `json:"communicationIssueStatus"`
	Disability               string `json:"disability,omitempty"`
	VclaimMemberCardNumber   string `json:"vclaimMember_cardNumber,omitempty"`
}

type PersonContact struct {
	TypeCode string `json:"type_code"`
	Value    string `json:"value"`
}

// AuthResponse menangkap kembalian token dari API Login
type AuthResponse struct {
	Token string `json:"token"` // Sesuaikan field ini dengan struktur asli response API tujuan (misal: "access_token")
}

// RunMigration adalah fungsi utama yang dipanggil oleh cronjob / scheduler
func RunMigration(ctx context.Context, cfg Config) {
	log.Println("[MIGRATOR] Memulai proses migrasi pasien...")

	// 1. Dapatkan Token API
	token, err := getAuthToken(cfg.BaseURL, cfg.Username, cfg.Password)
	if err != nil {
		log.Printf("[MIGRATOR] Gagal mendapatkan token: %v\n", err)
		return
	}
	log.Println("[MIGRATOR] Berhasil mendapatkan Auth Token.")

	// 2. Baca ID terakhir yang berhasil dikirim
	lastID := readLastID()
	log.Printf("[MIGRATOR] Melanjutkan migrasi dari ID pasien > %d\n", lastID)

	// 3. Looping migrasi satu per satu
	for {
		// Cek apakah context dibatalkan (misal service dimatikan / graceful shutdown)
		select {
		case <-ctx.Done():
			log.Println("[MIGRATOR] Proses dihentikan oleh sistem.")
			return
		default:
		}

		// Mengambil 1 data pasien berurutan berdasarkan ID
		patient, err := getNextPatient(cfg.DB, lastID)
		if err != nil {
			if err == sql.ErrNoRows {
				log.Println("[MIGRATOR] Selesai. Tidak ada lagi data pasien baru untuk dimigrasikan.")
				return
			}
			log.Printf("[MIGRATOR] Gagal mengambil data database: %v\n", err)
			return // Berhenti jika ada masalah database
		}

		log.Printf("[MIGRATOR] Memproses Pasien ID: %d, RM: %s, Nama: %s\n", patient.ID, patient.NoMR.String, patient.Nama.String)

		// Mapping data DB ke Payload API
		payload := buildPayload(patient)

		// Kirim data ke API
		err = sendPatientToAPI(cfg.BaseURL, token, payload)
		if err != nil {
			log.Printf("[MIGRATOR] Gagal mengirim Pasien ID %d ke API: %v\n", patient.ID, err)
			log.Printf("[MIGRATOR] Migrasi terhenti. Akan diulang dari ID %d pada putaran berikutnya.\n", lastID)
			return // Berhenti agar tidak ter-skip (Bisa diubah ke 'continue' jika ingin melewati data yg error)
		}

		// 4. Update file tracker dengan ID yang baru saja sukses
		lastID = patient.ID
		saveLastID(lastID)
		log.Printf("[MIGRATOR] Sukses mengirim Pasien ID %d\n", lastID)

		// Delay kecil agar tidak membebani server tujuan (Rate Limiting)
		time.Sleep(500 * time.Millisecond)
	}
}

// getNextPatient mengambil tepat 1 pasien setelah ID terakhir (ascending)
func getNextPatient(db *sql.DB, lastID int) (PatientDB, error) {
	var p PatientDB
	query := `
		SELECT 
			id, nomr, title, nama, tempat, tgllahir, jeniskelamin, 
			noktp, noktp_baru, no_kartu, pekerjaan, no_hp, disabilitas, 
			hambatan_komunikasi, txt_agama, txt_pendidikan, tgldaftar 
		FROM public.m_pasien 
		WHERE id > $1 
		ORDER BY id ASC 
		LIMIT 1`

	err := db.QueryRow(query, lastID).Scan(
		&p.ID, &p.NoMR, &p.Title, &p.Nama, &p.TempatLahir, &p.TglLahir, &p.JenisKelamin,
		&p.NoKTP, &p.NoKTPBaru, &p.NoKartu, &p.Pekerjaan, &p.NoHP, &p.Disabilitas, &p.HambatanKomunikasi,
		&p.TxtAgama, &p.TxtPendidikan, &p.TglDaftar,
	)
	return p, err
}

// buildPayload memetakan struktur database menjadi struktur JSON API
func buildPayload(dbData PatientDB) ApiPayload {
	// Format Gender (L/P ke male/female)
	gender := "unknown"
	if dbData.JenisKelamin.Valid {
		if strings.ToUpper(dbData.JenisKelamin.String) == "L" {
			gender = "male"
		} else if strings.ToUpper(dbData.JenisKelamin.String) == "P" {
			gender = "female"
		}
	}

	// Format BirthDate ke ISO8601 (RFC3339)
	birthDate := ""
	if dbData.TglLahir.Valid {
		birthDate = dbData.TglLahir.Time.Format(time.RFC3339)
	}

	// Format TglDaftar
	registeredAt := time.Now().Format(time.RFC3339)
	if dbData.TglDaftar.Valid {
		registeredAt = dbData.TglDaftar.Time.Format(time.RFC3339)
	}

	// Issue komunikasi (boolean mapping dasar)
	commIssue := false
	if dbData.HambatanKomunikasi.Valid && dbData.HambatanKomunikasi.String != "" && strings.ToLower(dbData.HambatanKomunikasi.String) != "tidak ada" {
		commIssue = true
	}

	// Susun Contacts array
	contacts := []PersonContact{}
	if dbData.NoHP.Valid && dbData.NoHP.String != "" {
		contacts = append(contacts, PersonContact{
			TypeCode: "phone",
			Value:    dbData.NoHP.String,
		})
	}

	// Logika NoKTP Baru vs NoKTP Lama
	ktp := ""
	if dbData.NoKTPBaru.Valid && strings.TrimSpace(dbData.NoKTPBaru.String) != "" {
		ktp = dbData.NoKTPBaru.String
	} else if dbData.NoKTP.Valid {
		ktp = dbData.NoKTP.String
	}

	return ApiPayload{
		Person: Person{
			Name:                     dbData.Nama.String,
			FrontTitle:               dbData.Title.String,
			EndTitle:                 "", // Dikosongkan sesuai format JSON
			BirthDate:                birthDate,
			BirthPlace:               dbData.TempatLahir.String,
			BirthRegencyCode:         "", // Dikosongkan sesuai format JSON
			GenderCode:               gender,
			ResidentIdentityNumber:   ktp,
			ReligionCode:             strings.ToLower(dbData.TxtAgama.String), // Asumsi text cocok dengan code API
			EducationCode:            dbData.TxtPendidikan.String,             // Asumsi text cocok dengan code API
			OccupationCode:           strings.ToLower(dbData.Pekerjaan.String),
			OccupationName:           dbData.Pekerjaan.String,
			Nationality:              "indonesia", // Default fallback
			CommunicationIssueStatus: commIssue,
			Disability:               dbData.Disabilitas.String,
			VclaimMemberCardNumber:   dbData.NoKartu.String,
		},
		PersonContacts: contacts,
		RegisteredAt:   registeredAt,
		NewBornStatus:  false, // Hardcoded seperti pada contoh
		StatusCode:     "active",
	}
}

// sendPatientToAPI melakukan HTTP POST request berisi data pasien
func sendPatientToAPI(baseURL, token string, payload ApiPayload) error {
	url := fmt.Sprintf("%s/v1/patient", strings.TrimRight(baseURL, "/"))

	jsonData, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("gagal marshal payload: %v", err)
	}

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	req.Header.Set("X-UsedRole", "func|reg")

	client := &http.Client{Timeout: 15 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("request gagal: %v", err)
	}
	defer res.Body.Close()

	body, _ := io.ReadAll(res.Body)

	// Anggap HTTP 200 dan 201 adalah sukses
	if res.StatusCode != http.StatusOK && res.StatusCode != http.StatusCreated {
		return fmt.Errorf("API merespon dengan status %d: %s", res.StatusCode, string(body))
	}

	return nil
}

// getAuthToken login dan mendapatkan token dari endpoint authentication
func getAuthToken(baseURL, username, password string) (string, error) {
	url := fmt.Sprintf("%s/v1/authentication/login", strings.TrimRight(baseURL, "/"))

	payload := map[string]string{
		"name":     username,
		"password": password,
	}
	jsonData, _ := json.Marshal(payload)

	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")

	client := &http.Client{Timeout: 10 * time.Second}
	res, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer res.Body.Close()

	if res.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(res.Body)
		return "", fmt.Errorf("login failed dengan status %d: %s", res.StatusCode, string(body))
	}

	var authRes AuthResponse
	if err := json.NewDecoder(res.Body).Decode(&authRes); err != nil {
		// Fallback parse jika kembalian json menggunakan field lain misal "token" atau "access_token"
		return "", fmt.Errorf("gagal decode response login: %v", err)
	}

	return authRes.Token, nil
}

// --- Tracker File Management ---

// readLastID membaca integer dari file tracker. Mengembalikan 0 jika file tidak ada/kosong.
func readLastID() int {
	data, err := os.ReadFile(trackerFile)
	if err != nil {
		return 0 // Default mulai dari awal (ID > 0) jika file belum pernah dibuat
	}

	idStr := strings.TrimSpace(string(data))
	if idStr == "" {
		return 0
	}

	id, err := strconv.Atoi(idStr)
	if err != nil {
		log.Printf("[WARNING] Format isi tracker file salah ('%s'). Mulai dari 0.\n", idStr)
		return 0
	}

	return id
}

// saveLastID menyimpan ID integer terakhir ke dalam file tracker
func saveLastID(id int) {
	err := os.WriteFile(trackerFile, []byte(strconv.Itoa(id)), 0644)
	if err != nil {
		log.Printf("[ERROR] Gagal menyimpan file tracker: %v\n", err)
	}
}
