package domain

import (
	"context"
	"errors"
	"nearle/db"
	"nearle/models"
	"nearle/utils"
	"net/http"
	"strconv"

	firebase "firebase.google.com/go"
	"firebase.google.com/go/messaging"
	"google.golang.org/api/option"
	"gorm.io/gorm"
)

var (
	firebaseApp *firebase.App
	fcmClient   *messaging.Client
)

func getFCMClient() (*messaging.Client, error) {
	if fcmClient != nil {
		return fcmClient, nil
	}

	opt := option.WithCredentialsFile("nearle-gear-firebase-adminsdk-l9oha-23ca3b3609.json")
	app, err := firebase.NewApp(context.Background(), nil, opt)
	if err != nil {
		return nil, errors.New("error initializing firebase app: " + err.Error())
	}
	firebaseApp = app

	client, err := app.Messaging(context.Background())
	if err != nil {
		return nil, errors.New("error getting messaging client: " + err.Error())
	}
	fcmClient = client
	return fcmClient, nil
}

func GetApplocations(aid int) []models.Applocations {

	var data []models.Applocations
	var q1 string

	if aid != 0 {

		q1 = "Select * from app_location where status='Active' and applocationid=?"
		db.DB.Raw(q1, aid).Find(&data)

	} else {

		q1 = "Select * from app_location where status='Active'"
		db.DB.Raw(q1).Find(&data)

	}

	utils.Logger.Debugf("Query executed for aid: %d", aid)

	return data

}

func GetApplocationsv2(userid int) []models.Applocations {

	var data []models.Applocations
	var q1 string

	if userid != 0 {
		q1 = `
			SELECT b.*
			FROM app_locationconfig a
			INNER JOIN app_location b 
				ON a.applocationid = b.applocationid
			WHERE b.status = 'Active'
			  AND a.userid = ?`
		db.DB.Raw(q1, userid).Find(&data)
	} else {
		q1 = `
			SELECT *
			FROM app_location
			WHERE status = 'Active'
		`
		db.DB.Raw(q1).Find(&data)
	}

	utils.Logger.Debugf("Query executed for userid: %d", userid)
	return data
}

func GetApplocationConfig(aid int) models.Applocations {

	var data models.Applocations
	var q1, q2 string

	if aid != 0 {

		q1 = `Select * from app_location where status='Active' and applocationid=` + strconv.Itoa(aid)
		q2 = `SELECT distinct a.userid,a.userfcmtoken,b.notify,b.applocationid,c.opentime,c.closetime 
			 FROM app_users a 
			 INNER JOIN app_locationconfig b ON a.userid=b.userid 
			 INNER JOiN app_location c on a.applocationid= c.applocationid
			 WHERE b.notify='true' AND b.applocationid=` + strconv.Itoa(aid)

	} else {

		q1 = `Select * from app_location where status='Active'`

	}

	// print(q1)

	// db.DB.Raw(q1).Preload("Applocationadmins").Find(&data)

	db.DB.Raw(q1).Preload("Applocationadmins", func(db *gorm.DB) *gorm.DB {
		return db.Raw(q2)
	}).Find(&data)

	return data

}

func GetAppPricing(aid, cid, pid int) []models.Apppricing {

	var data []models.Apppricing
	var q1 string

	if aid != 0 {

		q1 = `SELECT a.* FROM app_pricing a
		   JOIN (
			     SELECT pricingtypeid, MAX(pricingdate) AS maxpricingdate
			     FROM app_pricing
			     WHERE applocationid = ?
			     GROUP BY pricingtypeid
		    ) AS maxpricing ON a.pricingtypeid = maxpricing.pricingtypeid AND a.pricingdate = maxpricing.maxpricingdate WHERE a.applocationid = ?`

		db.DB.Raw(q1, aid, aid).Find(&data)

	} else if cid != 0 {

		q1 = `SELECT * FROM app_pricing WHERE pricingdate = (SELECT MAX(pricingdate) FROM app_pricing where configid= ?)`
		db.DB.Raw(q1, cid).Find(&data)

	}

	// print(q1)

	db.DB.Raw(q1).Find(&data)

	return data

}

func CreateAppProcing(input models.Apppricing) bool {

	err := db.DB.Table("app_pricing").Create(&input).Error

	return err == nil
}

func GetAllAppPricing(aid int) []models.Apppricing {

	var data []models.Apppricing
	var q1 string

	if aid != 0 {

		q1 = `SELECT a.*,b.locationname as applocation, d.appname FROM app_pricing a 
		inner join app_location b on a.applocationid=b.applocationid
		Inner Join app_types c on a.pricingtypeid=c.apptypeid
		INNER JOIN app_config d ON a.configid=d.configid
		where a.applocationid=? ORDER BY b.locationname ASC`

		db.DB.Raw(q1, aid).Find(&data)

	} else {

		q1 = `SELECT a.*,b.locationname as applocation, d.appname FROM app_pricing a 
		inner join app_location b on a.applocationid=b.applocationid
		Inner Join app_types c on a.pricingtypeid=c.apptypeid
		INNER JOIN app_config d ON a.configid=d.configid ORDER BY b.locationname ASC`

		db.DB.Raw(q1).Find(&data)

	}

	utils.Logger.Debugf("Query executed for aid: %d", aid)

	return data

}

func GetApplocationadmins(aid int) []models.Applocationadmins {

	var data []models.Applocationadmins
	var q1 string

	if aid != 0 {

		q1 = `SELECT distinct a.userid,a.userfcmtoken,b.notify,b.applocationid,c.opentime,c.closetime 
			 FROM app_users a 
			 INNER JOIN app_locationconfig b ON a.userid=b.userid 
			 INNER JOiN app_location c on a.applocationid= c.applocationid
			 WHERE b.notify='true' AND b.applocationid=?`

		db.DB.Raw(q1, aid).Find(&data)

	}

	return data

}

func GetAppconfig(cid int) models.Appconfig {

	var data models.Appconfig
	var q1 string

	if cid != 0 {

		q1 = `SELECT a.configid,a.Appname,a.paymentdevkey,a.paymentlivekey,a.fcmkey,a.googleapikey,a.applocationradius,
		b.providerid,b.providerapi,b.providerkey
		FROM app_config a 
		LEFT JOIN smsproviders b ON a.smsproviderid=b.providerid
		WHERE a.configid=?`
		db.DB.Raw(q1, cid).Find(&data)

	} else {

		q1 = `SELECT a.configid,a.Appname,a.paymentdevkey,a.paymentlivekey,a.fcmkey,a.googleapikey,a.applocationradius,
			b.providerid,b.providerapi,b.providerkey FROM app_config a 
			LEFT JOIN smsproviders b ON a.smsproviderid=b.providerid order by configid asc`
		db.DB.Raw(q1).Find(&data)

	}
	utils.Logger.Debugf("Query executed for cid: %d", cid)

	return data

}

func GetAllAppconfig() []models.Appconfig {

	var data []models.Appconfig

	q1 := `SELECT a.configid,a.Appname,a.paymentdevkey,a.paymentlivekey,a.fcmkey,a.googleapikey,a.applocationradius,
			b.providerid,b.providerapi,b.providerkey FROM app_config a 
			LEFT JOIN smsproviders b ON a.smsproviderid=b.providerid order by configid asc`

	db.DB.Raw(q1).Find(&data)

	return data

}

func GetAppModule() []models.AppModule {

	var data []models.AppModule

	q1 := `SELECT * FROM app_module where status='Active'`

	db.DB.Raw(q1).Find(&data)

	return data

}

func GetApptypes(tag string) []models.Apptypes {

	var data []models.Apptypes

	q1 := "Select * from app_types where status ='Active' and tag=?"
	db.DB.Raw(q1, tag).Find(&data)

	return data

}

func GetSubcategories(moduleid int, categoryid int) []models.Appsubcategories {
	var data []models.Appsubcategories

	query := `
	SELECT a.subcategoryid,a.categoryid,a.subcategoryname,b.categoryname,a.status,c.moduleid
	FROM app_subcategory a
	INNER JOIN app_category b ON a.categoryid = b.categoryid
	INNER JOIN app_module c ON a.categoryid = c.categoryid
	WHERE 1=1`

	var params []interface{}

	if moduleid != 0 {
		query += " AND c.moduleid = ?"
		params = append(params, moduleid)
	}

	if categoryid != 0 {
		query += " AND a.categoryid = ?"
		params = append(params, categoryid)
	}

	db.DB.Raw(query, params...).Scan(&data)
	return data
}

func GetCategories(mid int) []models.AppCategory {
	var data []models.AppCategory

	if mid == 0 {
		// No moduleid filter → return all categories
		q := `
			SELECT a.*, b.moduleid 
			FROM app_category a 
			INNER JOIN app_module b ON a.categoryid = b.categoryid
		`
		db.DB.Raw(q).Scan(&data)
	} else {
		// Filter by moduleid
		q := `
			SELECT a.*, b.moduleid 
			FROM app_category a 
			INNER JOIN app_module b ON a.categoryid = b.categoryid
			WHERE b.moduleid = ?
		`
		db.DB.Raw(q, mid).Scan(&data)
	}

	return data
}

func Insertnotification(input models.Data) bool {

	db.DB.Table("tenantnotifications").Create(&input)

	return true
}

func SendLoginNotification(regid, accessid string, pin int) error {
	if regid == "" {
		return errors.New("no FCM token provided")
	}

	client, err := getFCMClient()
	if err != nil {
		return err
	}

	// Construct the notification payload
	body := "Your passcode to Nearle app is: " + strconv.Itoa(pin) + "."
	title := "Nearle Code"

	// Prepare FCM message
	message := &messaging.Message{
		Notification: &messaging.Notification{
			Title: title,
			Body:  body,
		},
		Token: regid,
		Android: &messaging.AndroidConfig{
			Priority: "high",
			Notification: &messaging.AndroidNotification{
				Sound: "ring",
			},
		},
	}

	// Send the FCM notification
	response, err := client.Send(context.Background(), message)
	if err != nil {
		utils.Logger.Errorf("FCM send error: %v", err)
		return errors.New("failed to send FCM: " + err.Error())
	}

	utils.Logger.Infof("Successfully sent message: %v", response)
	return nil
}

func SendSingleNotification(regid, accessid string, Partner string) bool {

	client, err := getFCMClient()
	if err != nil {
		utils.Logger.Errorf("getFCMClient error: %v", err)
		return false
	}

	title := "Nearle Admin"
	body := "Orders had been placed for delivery by " + Partner

	message := &messaging.Message{
		Notification: &messaging.Notification{
			Title: title,
			Body:  body,
		},
		Token: regid,
		Android: &messaging.AndroidConfig{
			Priority: "high",
			Notification: &messaging.AndroidNotification{
				Sound: "ring",
			},
		},
	}

	response, err := client.Send(context.Background(), message)
	if err != nil {
		utils.Logger.Errorf("Error sending single notification: %v", err)
		return false
	}

	utils.Logger.Infof("Successfully sent single message: %v", response)
	return true
}

func NotifyUser(fcm models.NotifyUser) bool {

	client, err := getFCMClient()
	if err != nil {
		utils.Logger.Errorf("getFCMClient error: %v", err)
		return false
	}

	message := &messaging.Message{
		Notification: &messaging.Notification{
			Title: fcm.Notification.Notify.Title,
			Body:  fcm.Notification.Notify.Body,
		},
		Token: fcm.Notification.To,
		Android: &messaging.AndroidConfig{
			Priority: fcm.Notification.Priority,
			Notification: &messaging.AndroidNotification{
				Sound: fcm.Notification.Notify.Sound,
			},
		},
	}

	response, err := client.Send(context.Background(), message)
	if err != nil {
		utils.Logger.Errorf("Error notifying user: %v", err)
		return false
	}

	utils.Logger.Infof("Successfully notified user: %v", response)
	return true
}

func SendNotifications(fcm models.Notifications) bool {

	client, err := getFCMClient()
	if err != nil {
		utils.Logger.Errorf("getFCMClient error: %v", err)
		return false
	}

	successCount := 0
	for _, token := range fcm.Registrationids {
		if token == "" {
			continue
		}
		message := &messaging.Message{
			Notification: &messaging.Notification{
				Title: fcm.Notify.Title,
				Body:  fcm.Notify.Body,
			},
			Token: token,
			Android: &messaging.AndroidConfig{
				Priority: fcm.Priority,
				Notification: &messaging.AndroidNotification{
					Sound: fcm.Notify.Sound,
				},
			},
		}

		_, err := client.Send(context.Background(), message)
		if err != nil {
			utils.Logger.Errorf("Error sending notification to token %s: %v", token, err)
			continue
		}
		successCount++
	}

	utils.Logger.Infof("Successfully sent %d notifications out of %d", successCount, len(fcm.Registrationids))
	return successCount > 0
}

func SendNotification(fcm models.Notifications, accessid string) models.Result {

	var result models.Result

	client, err := getFCMClient()
	if err != nil {
		utils.Logger.Errorf("getFCMClient error: %v", err)
		result.Code = http.StatusInternalServerError
		result.Message = "Error initializing FCM client"
		result.Status = false
		return result
	}

	// Assuming we send to the first registration ID if multiple provided
	if len(fcm.Registrationids) == 0 {
		result.Code = http.StatusBadRequest
		result.Message = "No registration IDs provided"
		result.Status = false
		return result
	}

	message := &messaging.Message{
		Notification: &messaging.Notification{
			Title: fcm.Notify.Title,
			Body:  fcm.Notify.Body,
		},
		Token: fcm.Registrationids[0],
		Android: &messaging.AndroidConfig{
			Priority: fcm.Priority,
			Notification: &messaging.AndroidNotification{
				Sound: fcm.Notify.Sound,
			},
		},
	}

	response, err := client.Send(context.Background(), message)
	if err != nil {
		utils.Logger.Errorf("Error sending single notification: %v", err)
		result.Code = http.StatusInternalServerError
		result.Message = "Error sending notification: " + err.Error()
		result.Status = false
		return result
	}

	utils.Logger.Infof("Successfully sent notification: %v", response)

	status := Insertnotification(fcm.Data)
	if !status {
		result.Code = http.StatusConflict
		result.Message = "Notification sent but Insert UnSuccessfully"
		result.Status = false
		return result
	}

	result.Code = http.StatusCreated
	result.Message = "Notification Sent and Inserted Successfully"
	result.Status = true
	return result
}

func Getconfigs(categoryid int) models.Appconfig {
	var q1 string
	var data models.Appconfig

	q1 = `SELECT a.configid,a.appname,a.smsproviderid,COALESCE(a.fcmkey,'') AS fcmkey,COALESCE(a.clientid,'') AS clientid,COALESCE(a.googleapikey,'') AS googleapikey,COALESCE(a.paymentdevkey,'') AS paymentdevkey,COALESCE(a.paymentlivekey,'') AS paymentlivekey,a.applocationradius,COALESCE(b.providername,'') AS providername,COALESCE(b.providerapi,'') AS providerapi,COALESCE(b.providerkey,'') AS providerkey, COALESCE(b.username,'') AS username,COALESCE(b.passcode,'') AS passcode,COALESCE(b.defaultprovider,false) AS  defaultprovider 
	      FROM app_config a LEFT OUTER JOIN smsproviders b ON a.smsproviderid = b.providerid  AND b.status='Active' WHERE a.configid=?`

	db.DB.Raw(q1, categoryid).Find(&data)

	return data
}

func UpdateSeqno(tid int, prefix string) error {
	var field string

	if prefix == "ORD" {
		field = "orderseqno"
	} else if prefix == "INV" {
		field = "invoiceseqno"
	} else {
		return errors.New("invalid prefix: " + prefix)
	}

	result := db.DB.
		Table("ordersequences").
		Where("tenantid = ?", tid).
		Update(field, gorm.Expr(field+" + 1"))

	if result.Error != nil {
		utils.Logger.Errorf("UpdateSeqno failed for tenantid %d, prefix %s: %v", tid, prefix, result.Error)
		return result.Error
	}

	if result.RowsAffected == 0 {
		utils.Logger.Infof("No rows updated for tenantid %d with prefix %s", tid, prefix)
		return errors.New("no rows updated for tenantid " + strconv.Itoa(tid))
	}

	return nil
}

func GetSequenceno(tid int, prefix string) string {
	type SeqResult struct {
		Orderseqno string
	}

	var q1 string
	if prefix == "ORD" {
		q1 = `
			SELECT COALESCE(CONCAT(tenantid, '-', subprefix, COALESCE(MAX(orderseqno) + 1, 1)), '') AS orderseqno
			FROM ordersequences
			WHERE tenantid = ?
			GROUP BY tenantid, subprefix
		`
	} else if prefix == "INV" {
		q1 = `
			SELECT COALESCE(CONCAT(tenantid, '-', subprefix, COALESCE(MAX(invoiceseqno) + 1, 1)), '') AS orderseqno
			FROM ordersequences
			WHERE tenantid = ?
			GROUP BY tenantid, subprefix
		`
	}

	var result SeqResult
	db.DB.Raw(q1, tid).Scan(&result)
	return result.Orderseqno
}

func GetTenantNotifications(tenantid, locationid, customerid, moduleid, pageno, pagesize int) ([]models.TenantNotification, int) {

	var data []models.TenantNotification
	var total int

	offset := (pageno - 1) * pagesize

	baseQuery := db.DB.Table("tenantnotifications").
		Select("*").
		Where("tenantid = ?", tenantid)

	if locationid != 0 {
		baseQuery = baseQuery.Where("locationid = ?", locationid)
	}
	if customerid != 0 {
		baseQuery = baseQuery.Where("customerid = ?", customerid)
	}
	if moduleid != 0 {
		baseQuery = baseQuery.Where("moduleid = ?", moduleid)
	}

	err := baseQuery.
		Order("notificationdate DESC").
		Limit(pagesize).
		Offset(offset).
		Find(&data).Error

	if err != nil {
		utils.Logger.Errorf("DB Error GetTenantNotifications: %v", err)
	}

	return data, total
}

func GetUserBonusSummary(userid int) (models.Riderinfo, error) {

	var user models.Riderinfo
	query := `
		SELECT COALESCE(SUM(a.bonuspts), 0) AS bonuspts,b.*
		FROM deliveries a
		LEFT JOIN app_users b ON a.userid = b.userid
		WHERE a.userid = ?
		GROUP BY b.userid
	`

	err := db.DB.Raw(query, userid).Scan(&user).Error
	if err != nil {
		return user, err
	}

	return user, nil
}

func CreateAppLocationConfig(input models.AppLocationConfigRequest) bool {

	if input.Notify == "" {
		input.Notify = "true"
	}
	if input.Status == "" {
		input.Status = "Active"
	}

	for _, locID := range input.Applocationids {

		data := models.AppLocationConfig{
			Applocationid: locID,
			Configid:      input.Configid,
			Userid:        input.Userid,
			Partnerid:     input.Partnerid,
			Notify:        input.Notify,
			Status:        input.Status,
		}

		if err := db.DB.Table("app_locationconfig").Create(&data).Error; err != nil {
			return false
		}
	}

	return true
}

func UpdateAppLocationConfig(input models.AppLocationConfigRequest) bool {

	if input.Status == "" {
		input.Status = "Active"
	}

	for _, locID := range input.Applocationids {

		if err := db.DB.Table("app_locationconfig").
			Where("userid = ? AND applocationid = ?", input.Userid, locID).
			Update("status", input.Status).Error; err != nil {
			utils.Logger.Errorf("Error updating app_locationconfig: %v", err)
			return false
		}
	}

	return true
}

func GetUserRoles() ([]models.UserRoles, error) {

	var roles []models.UserRoles

	query := `SELECT * FROM app_roles
	`

	err := db.DB.Raw(query).Scan(&roles).Error
	if err != nil {
		return roles, err
	}

	return roles, nil
}
