package repositories

import (
	"errors"
	"fmt"
	"nearle/models"
	"strconv"
	"strings"

	"github.com/jinzhu/copier"
	"gorm.io/gorm"
)

type TenantRepository interface {
	SearchTenant(status, searchstr string) ([]models.Tenantinfo, error)
	GetAllTenants(pageno, pagesize, aid int, status, tenanttype, keyword string) ([]models.Tenantinfo, error)
	GetTenantLocations(tid int) ([]models.Tenantlocations, error)
	GetTenantSlot() (models.Tenantslot, error)
	CreateTenantCustomer(customer models.Tenantcustomer) (*models.Tenantcustomer, error)
	GetCustomerTenants(customerID int, categoryID int, tenantFlag int) ([]models.TenantInfo, error)
	GetTenantPricing(tid, aid int) (*models.Tenantpricing, error)
	UpdateLocation(input models.Tenantlocations) error
	CreateLocation(data models.Tenantlocations) error
	DeleteLocation(locationid int, tenantid int) error
	GetStaffs(tid int) ([]models.StaffInfo, error)
	CreateStaff(user models.User) error
	UpdateStaff(user models.User) error
	CreateTenantLocation(data models.Tenantlocations) (models.Tenantlocations, error)
	UpdateTenantLocation(data models.Tenantlocations) error
	CheckTenantByNo(cno string) int
	CreateTenantUser(data models.Tenants) (bool, error)
	GetUserByNo(cno string) models.UserInfo
	GetTenantByID(tid int, locationid int, userid int) (models.Tenantinfo, error)
	GetTenantByKeyword(keyword string) ([]models.TenantSearch, error)
}

type tenantRepository struct {
	db *gorm.DB
}

func NewTenantRepository(db *gorm.DB) TenantRepository {
	return &tenantRepository{db: db}
}

func (r *tenantRepository) SearchTenant(status, keyword string) ([]models.Tenantinfo, error) {
	var data []models.Tenantinfo
	var query string

	searchStr := strings.ToLower(keyword)

	if strings.ToLower(status) != "pending" {
		query = `
			SELECT a.*, b.subcategoryname, c.firstname, c.lastname, 
				   CONCAT(c.firstname, ' ', c.lastname) AS accountname 
			FROM tenants a
			INNER JOIN app_subcategory b ON a.subcategoryid = b.subcategoryid
			LEFT JOIN app_users c ON c.userid = a.partneruserid
			WHERE a.approved = 1 
			  AND LOWER(a.status) = ? 
			  AND LOWER(a.tenantname) LIKE ?
		`
		r.db.Raw(query, strings.ToLower(status), searchStr+"%").Scan(&data)
	} else {
		query = `
			SELECT a.*, b.subcategoryname, c.firstname, c.lastname, 
				   CONCAT(c.firstname, ' ', c.lastname) AS accountname 
			FROM tenants a
			INNER JOIN app_subcategory b ON a.subcategoryid = b.subcategoryid
			LEFT JOIN app_users c ON c.userid = a.partneruserid
			WHERE a.approved = 0 
			  AND LOWER(a.tenantname) LIKE ?
		`
		r.db.Raw(query, searchStr+"%").Scan(&data)
	}

	return data, nil
}

func (r *tenantRepository) GetAllTenants(pageno, pagesize, aid int, status, tenanttype, keyword string) ([]models.Tenantinfo, error) {
	offset := (pageno - 1) * pagesize

	var data []models.Tenantinfo

	base := `SELECT * FROM tenants a WHERE 1 = 1`

	var (
		conds  []string
		params []interface{}
	)

	switch strings.ToLower(status) {
	case "active":
		conds = append(conds, "a.approved = 1 AND a.status = 'Active'")
	case "inactive":
		conds = append(conds, "a.approved = 1 AND a.status = 'InActive'")
	case "pending":
		conds = append(conds, "a.approved = 0")
	}

	if aid != 0 {
		conds = append(conds, "a.applocationid = ?")
		params = append(params, aid)
	}

	if tenanttype != "" {
		conds = append(conds, "a.tenanttype = ?")
		params = append(params, tenanttype)
	}

	if keyword != "" {

		kw := "%" + strings.ToLower(keyword) + "%"

		conds = append(conds,
			"(LOWER(a.tenantname) LIKE ? OR LOWER(a.primarycontact) LIKE ?)")

		params = append(params, kw, kw)
	}

	if len(conds) > 0 {
		base += " AND " + strings.Join(conds, " AND ")
	}

	base += " ORDER BY a.tenantid DESC LIMIT ? OFFSET ?"
	params = append(params, pagesize, offset)

	err := r.db.Raw(base, params...).Scan(&data).Error
	if err != nil {
		return nil, err
	}

	return data, nil
}

func (r *tenantRepository) GetTenantLocations(tid int) ([]models.Tenantlocations, error) {
	var data []models.Tenantlocations

	q1 := `SELECT * FROM tenantlocations WHERE tenantid = ?`

	if err := r.db.Raw(q1, tid).Find(&data).Error; err != nil {
		return nil, err
	}

	for i := range data {
		data[i].Status = strings.ToLower(data[i].Status)
	}

	return data, nil
}

func (r *tenantRepository) GetTenantSlot() (models.Tenantslot, error) {
	var data models.Tenantslot

	err := r.db.Raw(`SELECT * FROM tenantslot`).Find(&data).Error

	if err != nil {
		return models.Tenantslot{}, err
	}

	return data, nil
}

func (r *tenantRepository) CreateTenantCustomer(customer models.Tenantcustomer) (*models.Tenantcustomer, error) {
	var existing models.Tenantcustomer

	// 🔍 Step 1: Check if a record already exists with same customerid and locationid
	err := r.db.
		Where("customerid = ? AND locationid = ?", customer.CustomerID, customer.LocationID).
		First(&existing).Error

	// If record found, prevent insertion
	if err == nil {
		return nil, fmt.Errorf("customer already exists for this location")
	}

	// If error other than record not found, return it
	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, err
	}

	// ✅ Step 2: Insert new record if no duplicate found
	if err := r.db.Create(&customer).Error; err != nil {
		return nil, err
	}

	return &customer, nil
}

func (r *tenantRepository) GetCustomerTenants(customerID int, categoryID int, tenantFlag int) ([]models.TenantInfo, error) {
	var tenants []models.TenantInfo

	query := `
        SELECT a.customerid, a.locationid, b.tenantid,b.tenantname,b.address,b.licenseno,
               b.primaryemail,b.primarycontact,b.applocationid,b.suburb,b.city,
               b.latitude,b.longitude,b.postcode,b.tenantimage,b.subcategoryid,
               b.categoryid,b.registrationno,d.userfcmtoken,c.locationname,
               COALESCE(o.orderscount, 0) AS orderscount
        FROM tenantcustomers a
        LEFT JOIN tenants b ON a.tenantid = b.tenantid
        INNER JOIN tenantlocations c ON a.locationid = c.locationid
        LEFT JOIN (
            SELECT tenantid, customerid, COUNT(*) AS orderscount
            FROM orders
            GROUP BY tenantid, customerid
        ) o ON b.tenantid = o.tenantid AND o.customerid = a.customerid
        LEFT JOIN (
            SELECT locationid, 
                   MAX(userfcmtoken) AS userfcmtoken
            FROM app_users
            GROUP BY locationid
        ) d ON d.locationid = a.locationid
        WHERE a.customerid = ? 
          AND b.tenantid IS NOT NULL
    `

	args := []interface{}{customerID}

	if categoryID != 0 {
		query += " AND b.categoryid = ?"
		args = append(args, categoryID)
	}

	if tenantFlag == 1 {
		query += " AND COALESCE(o.orderscount,0) > 0"
	}

	if err := r.db.Raw(query, args...).Scan(&tenants).Error; err != nil {
		return nil, err
	}

	if tenants == nil {
		return []models.TenantInfo{}, nil
	}

	// Attach top 5 subcategories
	if len(tenants) > 0 {
		var subcategories []models.ProductSubcategory
		if err := r.db.Table("productsubcategories").
			Order("subcatid ASC").
			Limit(5).
			Find(&subcategories).Error; err != nil {
			return nil, err
		}

		for i := range tenants {
			tenants[i].ProductSubcategory = subcategories
		}
	}

	print(tenants)

	return tenants, nil
}

func (r *tenantRepository) GetTenantPricing(tid, aid int) (*models.Tenantpricing, error) {
	var data models.Tenantpricing
	var q1 string

	if tid != 0 {
		q1 = `SELECT * 
		      FROM tenantpricing 
		      WHERE pricingdate = (
		          SELECT MAX(pricingdate) 
		          FROM tenantpricing 
		          WHERE tenantid=` + strconv.Itoa(tid) + `
		      ) 
		      AND tenantid=? 
		      ORDER BY tenantpricingid DESC`
	}

	if err := r.db.Raw(q1, tid).Find(&data).Error; err != nil {
		return nil, err
	}

	return &data, nil
}

func (r *tenantRepository) UpdateLocation(input models.Tenantlocations) error {
	tx := r.db.Begin()

	if err := tx.Where("locationid=?", input.Locationid).Updates(&input).Error; err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Commit().Error; err != nil {
		return err
	}

	return nil
}

func (r *tenantRepository) DeleteLocation(locationid int, tenantid int) error {
	tx := r.db.Begin()

	if err := tx.Where("locationid=? AND tenantid=?", locationid, tenantid).Delete(&models.Tenantlocations{}).Error; err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Commit().Error; err != nil {
		return err
	}

	return nil
}

func (r *tenantRepository) CreateLocation(data models.Tenantlocations) error {
	if err := r.db.Create(&data).Error; err != nil {
		return err
	}
	return nil
}

func (r *tenantRepository) GetStaffs(tid int) ([]models.StaffInfo, error) {
	var data []models.StaffInfo

	q1 := `SELECT a.userid,a.firstname,a.lastname,
	           CONCAT(a.firstname,' ',a.lastname) AS fullname,
	           a.email,a.contactno,a.address,a.suburb,a.city,
	           a.state,a.postcode,a.userfcmtoken,a.pin,a.applocationid,
	           a.roleid,a.partnerid,a.tenantid,a.locationid,
	           b.locationname
	        FROM app_users a
	        INNER JOIN tenantlocations b ON a.locationid = b.locationid
	        WHERE a.tenantid = ?`

	if err := r.db.Raw(q1, tid).Scan(&data).Error; err != nil {
		return nil, err
	}

	return data, nil
}

func (r *tenantRepository) CreateStaff(user models.User) error {
	if err := r.db.Table("app_users").Create(&user).Error; err != nil {
		return err
	}
	return nil
}

func (r *tenantRepository) UpdateStaff(user models.User) error {
	if err := r.db.Table("app_users").Where("userid = ?", user.Userid).Updates(&user).Error; err != nil {
		return err
	}
	return nil
}

func (r *tenantRepository) CreateTenantLocation(data models.Tenantlocations) (models.Tenantlocations, error) {
	var user models.Tenantuser
	tx := r.db.Begin()

	// Default to Active if the caller didn't specify — matches
	// tenantlocations' own gorm default and the primary-location behavior
	// from tenant onboarding. Forcing InActive here used to also block the
	// spawned manager login (AppLogin checks account status before it ever
	// gets to the "no password set" branch), so a new store's login could
	// never reach the password-setup screen.
	if data.Status == "" {
		data.Status = "Active"
	}

	// Step 1: Insert into tenantlocations. GORM writes the DB-assigned
	// locationid back onto data, which callers need to build the store's
	// QR code (payload is just {tenantid, locationid}) right after onboarding.
	if err := tx.Create(&data).Error; err != nil {
		tx.Rollback()
		return models.Tenantlocations{}, err
	}

	// Step 2: Insert into app_users
	user.Authname = data.Email
	user.Firstname = data.Locationname
	user.Email = data.Email
	user.Contactno = data.Contactno
	user.Address = data.Address
	user.Suburb = data.Suburb
	user.City = data.City
	user.State = data.State
	user.Postcode = data.Postcode
	user.Partnerid = data.Partnerid
	user.Tenantid = data.Tenantid
	user.Locationid = data.Locationid
	user.Applocationid = data.Applocationid
	user.Configid = 1
	user.Status = data.Status
	user.Roleid = 0
	user.Authmode = 0
	user.Password = ""
	user.Dialcode = "+91"

	if err := tx.Table("app_users").Create(&user).Error; err != nil {
		tx.Rollback()
		return models.Tenantlocations{}, err
	}

	// Commit
	if err := tx.Commit().Error; err != nil {
		return models.Tenantlocations{}, err
	}

	return data, nil
}

func (r *tenantRepository) UpdateTenantLocation(input models.Tenantlocations) error {
	tx := r.db.Begin()

	// ✅ Step 1: Prepare map for tenantlocations update
	locationUpdate := make(map[string]interface{})

	if input.Locationname != "" {
		locationUpdate["locationname"] = input.Locationname
	}
	if input.Email != "" {
		locationUpdate["email"] = input.Email
	}
	if input.Contactno != "" {
		locationUpdate["contactno"] = input.Contactno
	}
	if input.Address != "" {
		locationUpdate["address"] = input.Address
	}
	if input.Suburb != "" {
		locationUpdate["suburb"] = input.Suburb
	}
	if input.City != "" {
		locationUpdate["city"] = input.City
	}
	if input.State != "" {
		locationUpdate["state"] = input.State
	}
	if input.Postcode != "" {
		locationUpdate["postcode"] = input.Postcode
	}
	if input.Partnerid != 0 {
		locationUpdate["partnerid"] = input.Partnerid
	}
	if input.Tenantid != 0 {
		locationUpdate["tenantid"] = input.Tenantid
	}
	if input.Applocationid != 0 {
		locationUpdate["applocationid"] = input.Applocationid
	}
	if input.Status != "" {
		locationUpdate["status"] = input.Status
	}

	// ✅ Step 2: Update tenantlocations (only provided fields)
	if len(locationUpdate) > 0 {
		if err := tx.Table("tenantlocations").
			Where("locationid = ?", input.Locationid).
			Updates(locationUpdate).Error; err != nil {
			tx.Rollback()
			return err
		}
	}

	// ✅ Step 3: Prepare map for app_users update (only matching fields)
	userUpdate := make(map[string]interface{})

	if input.Locationname != "" {
		userUpdate["firstname"] = input.Locationname
	}
	if input.Email != "" {
		userUpdate["email"] = input.Email
	}
	if input.Contactno != "" {
		userUpdate["contactno"] = input.Contactno
	}
	if input.Address != "" {
		userUpdate["address"] = input.Address
	}
	if input.Suburb != "" {
		userUpdate["suburb"] = input.Suburb
	}
	if input.City != "" {
		userUpdate["city"] = input.City
	}
	if input.State != "" {
		userUpdate["state"] = input.State
	}
	if input.Postcode != "" {
		userUpdate["postcode"] = input.Postcode
	}
	if input.Tenantid != 0 {
		userUpdate["tenantid"] = input.Tenantid
	}
	if input.Partnerid != 0 {
		userUpdate["partnerid"] = input.Partnerid
	}
	if input.Applocationid != 0 {
		userUpdate["applocationid"] = input.Applocationid
	}
	if input.Status != "" {
		userUpdate["status"] = input.Status
	}
	// ✅ Step 4: Update app_users (only provided fields)
	if len(userUpdate) > 0 {
		if err := tx.Table("app_users").
			Where("locationid = ?", input.Locationid).
			Updates(userUpdate).Error; err != nil {
			tx.Rollback()
			return err
		}
	}

	// ✅ Commit transaction
	if err := tx.Commit().Error; err != nil {
		return err
	}

	return nil
}

// ✅ Check if tenant exists
func (r *tenantRepository) CheckTenantByNo(cno string) int {
	var id int
	q1 := "SELECT tenantid FROM tenants WHERE primarycontact = '" + cno + `'`
	r.db.Raw(q1).Find(&id)
	return id
}

// ✅ Create tenant + user + customer records
func (r *tenantRepository) CreateTenantUser(data models.Tenants) (bool, error) {
	var seq models.Ordersequences
	var user models.User
	var cust models.Customers
	var custloc models.Customerlocations
	var tcust models.Tenantcustomers

	tx := r.db.Begin()

	// Step 1: Insert into tenants
	if err := tx.Create(&data).Error; err != nil {
		tx.Rollback()
		return false, errors.New("error in tenant creation")
	}

	// Step 2: Create order sequence
	seq.Tenantid = data.Tenantid
	if err := tx.Table("ordersequences").Create(&seq).Error; err != nil {
		tx.Rollback()
		return false, errors.New("error in sequence")
	}

	// Step 3: Create app_user
	if err := copier.Copy(&user, &data); err != nil {
		tx.Rollback()
		return false, err
	}
	user.Userfcmtoken = data.Tenanttoken
	user.Contactno = data.Primarycontact
	user.Email = data.Primaryemail
	user.Authname = data.Primaryemail
	user.Deviceid = data.Deviceid
	user.Tenantid = data.Tenantid
	user.Locationid = data.Tenantlocations.Locationid
	user.Roleid = 1
	// The onboarding form never sends a tenant configid, so copier.Copy left
	// this at zero — AppLogin's GetUserByAuthname always queries configid=1
	// for the web login, so a zero here makes the account permanently
	// unfindable by email no matter what's typed.
	user.Configid = 1

	if err := tx.Table("app_users").Create(&user).Error; err != nil {
		tx.Rollback()
		return false, errors.New("error in user creation")
	}

	// Step 4: Create / Update customers
	cust.Configid = data.Configid
	cust.Firstname = data.Tenantname
	cust.Email = data.Primaryemail
	cust.Contactno = data.Primarycontact
	cust.Deviceid = data.Deviceid
	cust.Devicetype = data.Devicetype
	cust.Customertoken = data.Tenanttoken
	cust.Profileimage = data.Tenantimage
	cust.Address = data.Address
	cust.Suburb = data.Suburb
	cust.City = data.City
	cust.State = data.State
	cust.Postcode = data.Postcode
	cust.Applocationid = data.Applocationid
	cust.Latitude = data.Latitude
	cust.Longitude = data.Longitude
	cust.Primaryaddress = 1

	cid := r.CheckCustomer(data.Primarycontact)

	if cid == 0 {
		if err := tx.Table("customers").Create(&cust).Error; err != nil {
			tx.Rollback()
			return false, errors.New("error in customer creation")
		}

		if err := copier.Copy(&custloc, &cust); err != nil {
			tx.Rollback()
			return false, err
		}

		if err := tx.Table("customerlocations").Create(&custloc).Error; err != nil {
			tx.Rollback()
			return false, errors.New("error in customer location")
		}
	} else {
		if err := tx.Table("customers").Where("customerid=?", cid).Updates(&cust).Error; err != nil {
			tx.Rollback()
			return false, errors.New("error updating customer")
		}

		if err := copier.Copy(&custloc, &cust); err != nil {
			tx.Rollback()
			return false, err
		}

		if err := tx.Table("customerlocations").Where("customerid=?", cid).Updates(&custloc).Error; err != nil {
			tx.Rollback()
			return false, errors.New("error updating customer location")
		}
	}

	// Step 5: Create tenant-customer link
	tcust.Customerid = cust.Customerid
	tcust.Tenantid = data.Tenantid
	tcust.Locationid = data.Tenantlocations.Locationid

	if err := tx.Table("tenantcustomers").Create(&tcust).Error; err != nil {
		tx.Rollback()
		return false, errors.New("error in tenant customer")
	}

	// ✅ Commit transaction
	if err := tx.Commit().Error; err != nil {
		return false, errors.New("error in tenant creation")
	}

	return true, nil
}

// ✅ Check if customer exists
func (r *tenantRepository) CheckCustomer(cno string) int {
	var id int
	q := "SELECT customerid FROM customers WHERE contactno = '" + cno + `'`
	r.db.Raw(q).Find(&id)
	return id
}

// ✅ Get user by contact number
func (r *tenantRepository) GetUserByNo(cno string) models.UserInfo {
	var user models.UserInfo

	q1 := `SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno,
	a.firstname,a.lastname,CONCAT(a.firstname,' ',a.lastname) AS fullname,
	a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.tenantid,a.locationid,
	b.partnerid,b.moduleid,b.categoryid,b.subcategoryid,
	b.applocationid,b.tenantname,b.address AS tenantaddress,b.state AS tenantstate,b.city AS tenantcity,
	b.postcode AS tenantpostcode,b.latitude AS tenantlat,b.longitude AS tenantlong
	FROM app_users a 
	LEFT JOIN tenants b ON a.tenantid = b.tenantid
	WHERE a.contactno = '` + cno + `'`

	r.db.Raw(q1).Find(&user)
	return user
}

func (r *tenantRepository) GetTenantByID(tid int, locationid int, userid int) (models.Tenantinfo, error) {
	var data models.Tenantinfo

	if locationid == 0 && userid > 0 {
		var userLocationID int
		err := r.db.Table("app_users").Select("locationid").Where("userid = ?", userid).Row().Scan(&userLocationID)
		if err == nil && userLocationID > 0 {
			locationid = userLocationID
		}
	}

	q1 := `
        SELECT a.*,b.categoryname,c.locationname AS applocation, d.allocationid AS allocationmode,e.typename AS allocationtype,e.mapid AS allocationid,f.locationid,
            f.locationname, f.contactno as locationcontact
        FROM tenants a
        INNER JOIN app_category b ON a.categoryid = b.categoryid
        INNER JOIN app_location c ON a.applocationid = c.applocationid
        LEFT JOIN partnerinfo d ON a.partnerid = d.partnerid
        LEFT JOIN app_types e ON d.allocationid = e.apptypeid
        LEFT JOIN tenantlocations f ON a.tenantid = f.tenantid
        WHERE a.tenantid = ?
    `

	var args []interface{}
	args = append(args, tid)

	if locationid != 0 {
		q1 += " AND f.locationid = ?"
		args = append(args, locationid)
	}

	if err := r.db.Raw(q1, args...).Find(&data).Error; err != nil {
		return data, err
	}

	data.Status = strings.ToLower(data.Status)

	return data, nil
}

func (r *tenantRepository) GetTenantByKeyword(keyword string) ([]models.TenantSearch, error) {
	var data []models.TenantSearch

	kw := "%" + strings.ToLower(keyword) + "%"

	query := `
		SELECT a.tenantname, b.productname, c.subcatname 
		FROM tenants a
		LEFT JOIN products b ON a.tenantid = b.tenantid
		LEFT JOIN productsubcategories c ON b.subcategoryid = c.subcatid
		WHERE c.categoryid = 2
		AND (LOWER(a.tenantname) LIKE ? OR LOWER(b.productname) LIKE ? OR LOWER(c.subcatname) LIKE ?)
	`

	if err := r.db.Raw(query, kw, kw, kw).Scan(&data).Error; err != nil {
		return nil, err
	}

	return data, nil
}
