package repositories

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

	"gorm.io/gorm"
)

type CustomerRepository interface {
	GetCustomer(cid int, cno string) (*models.CustomerInfo, error)
	UpdateCustomer(input models.Customers) error
	GetCustomerLocations(cid int) ([]models.Customerlocations, error)
	CreateCustomerLocation(input models.Customerlocations) (int, error)
	CreateCustomerRequest(req *models.CustomerRequest) error
	GetCustomerRequests(customerID int, pageNo, pageSize int) ([]models.CustomerRequest, int64, error)
	GetTenantCustomers(tid, lid, pageno, pagesize int, keyword string) []models.CustomerInfo
	SearchCustomer(keyword string, tid int) []models.CustomerInfo
	CheckCustomer(contactno string) int
	CheckTenantCustomer(cid, tid int) int
	CreateTenantCustomer(input models.Customers) int
	CreateCustomer(input models.Customers) int
	GetCustomerByContactNo(contactNo string) (*models.Customers, error)
}

type customerRepository struct {
	db *gorm.DB
}

func NewCustomerRepository(db *gorm.DB) CustomerRepository {
	return &customerRepository{db: db}
}

func (r *customerRepository) GetCustomer(cid int, cno string) (*models.CustomerInfo, error) {
	var data models.CustomerInfo

	q := `
		SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email,a.profileimage,a.dialcode,
		       a.deviceid,a.devicetype,a.authmode,a.configid,a.customertoken,a.intro,a.gender,a.dob,
		       b.locationid as deliverylocationid,b.address,b.suburb,b.city,b.state,b.landmark,b.doorno,b.postcode,
		       b.latitude,b.longitude,a.applocationid,
		       c.locationid as tenantlocationid,c.tenantid,
		       a.status,
		       d.qrmode,d.latitude as selectedlatitude,d.longitude as selectedlongitude,d.radius,d.applocationid,
		       e.allocationid
		FROM customers a
		INNER JOIN customerlocations b ON a.customerid=b.customerid AND b.primaryaddress=1
		INNER JOIN tenantcustomers c ON a.customerid=c.customerid
		LEFT JOIN app_location d ON a.applocationid=d.applocationid
		LEFT JOIN tenants e ON e.tenantid=c.tenantid
		WHERE %s LIMIT 1
	`

	var err error
	if cid != 0 {
		err = r.db.Raw(fmt.Sprintf(q, "a.customerid = ?"), cid).Scan(&data).Error
	} else {
		err = r.db.Raw(fmt.Sprintf(q, "a.contactno = ?"), cno).Scan(&data).Error
	}

	if err != nil {
		return nil, err
	}
	return &data, nil
}

func (r *customerRepository) UpdateCustomer(input models.Customers) error {
	var custloc models.Customerlocations
	tx := r.db.Begin()

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

	custloc.Customerid = input.Customerid
	custloc.Address = input.Address
	custloc.Suburb = input.Suburb
	custloc.City = input.City
	custloc.State = input.State
	custloc.Postcode = input.Postcode
	custloc.Latitude = input.Latitude
	custloc.Longitude = input.Longitude
	custloc.Doorno = input.Doorno
	custloc.Landmark = input.Landmark

	if err := tx.Table("customerlocations").
		Where("customerid=? and primaryaddress=?", input.Customerid, 1).
		Updates(&custloc).Error; err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Commit().Error; err != nil {
		fmt.Println("Commit failed:", err)
		return err
	}

	return nil
}

func (r *customerRepository) GetCustomerLocations(cid int) ([]models.Customerlocations, error) {
	var data []models.Customerlocations

	if cid == 0 {
		return data, nil
	}

	query := `SELECT a.locationid, a.customerid, b.applocationid, a.address, a.suburb, 
	                 a.city, a.state, a.landmark, a.doorno, a.postcode, a.latitude, 
	                 a.longitude, a.defaultaddress, a.primaryaddress, a.status
	          FROM customerlocations a 
			  LEFT JOIN customers b ON a.customerid = b.customerid 
			  WHERE a.customerid = ?`

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

	return data, nil
}

func (r *customerRepository) CreateCustomerLocation(input models.Customerlocations) (int, error) {

	// Duplicate address checking
	var count int
	err := r.db.Raw(`
		SELECT COUNT(*) FROM customerlocations 
		WHERE customerid = ? 
		AND address = ? 
		AND suburb = ? 
		AND city = ?
		AND state = ?
		AND postcode = ?
		AND landmark = ?
		AND doorno = ?
	`, input.Customerid, input.Address, input.Suburb, input.City, input.State,
		input.Postcode, input.Landmark, input.Doorno).
		Scan(&count).Error

	if err != nil {
		return 0, err
	}

	if count > 0 {
		// Duplicate found
		return -1, nil
	}

	// Insert new address
	if err := r.db.Create(&input).Error; err != nil {
		return 0, err
	}

	return input.Customerid, nil
}

func (r *customerRepository) CreateCustomerRequest(req *models.CustomerRequest) error {
	return r.db.Table("customerrequest").Create(req).Error
}

func (r *customerRepository) GetCustomerRequests(customerID, pageNo, pageSize int) ([]models.CustomerRequest, int64, error) {
	var requests []models.CustomerRequest
	var total int64

	query := r.db.Table("customerrequest")

	if customerID != 0 {
		query = query.Where("customerid = ?", customerID)
	}

	query.Count(&total)

	offset := (pageNo - 1) * pageSize
	if err := query.Order("created desc").Offset(offset).Limit(pageSize).Find(&requests).Error; err != nil {
		return nil, 0, err
	}

	return requests, total, nil
}

func (r *customerRepository) GetTenantCustomers(tid, lid, pageno, pagesize int, keyword string) []models.CustomerInfo {
	var data []models.CustomerInfo
	offset := (pageno - 1) * pagesize

	var q1 string
	var args []interface{}
	searchLike := "%" + keyword + "%"

	// DISTINCT ON collapses to one row per customer BEFORE the LIMIT is applied.
	// Without it the store-scoped branch below paginated the joined
	// customerlocations rows — one per saved address — so `pagesize` bought a
	// page of addresses, not of customers. Live example: locationid 1185 returned
	// 12 rows that were only 2 people, 11 of them one customer's addresses. A
	// store with a page size of 20 therefore listed roughly three customers and
	// gave no hint that the rest existed.
	//
	// The ORDER BY must lead with the DISTINCT ON expression, so customerid sorts
	// first; the trailing keys only decide WHICH address represents a customer,
	// preferring the one flagged primary.
	if lid != 0 {
		q1 = `SELECT DISTINCT ON (a.customerid) a.customerid,a.firstname,a.lastname,a.contactno,a.email,
			b.locationid as deliverylocationid,b.address,b.suburb,b.city,b.state,b.landmark,b.doorno,b.postcode,
			b.latitude,b.longitude,a.applocationid,c.locationid as tenantlocationid,a.status
			FROM customers a
			LEFT JOIN customerlocations b ON a.customerid=b.customerid
			INNER JOIN tenantcustomers c ON a.customerid=c.customerid
			WHERE c.locationid = ? AND c.tenantid = ?`

		args = append(args, lid, tid)

		if keyword != "" {
			q1 += ` AND (a.firstname LIKE ? OR a.contactno LIKE ? OR b.suburb LIKE ?)`
			args = append(args, searchLike, searchLike, searchLike)
		}

		q1 += ` ORDER BY a.customerid DESC, b.primaryaddress DESC NULLS LAST, b.locationid ASC
			LIMIT ? OFFSET ?`
		args = append(args, pagesize, offset)

	} else {
		// A customer linked to several outlets of the same tenant has one
		// tenantcustomers row per outlet, so this branch double-counted them
		// against the LIMIT too.
		q1 = `SELECT DISTINCT ON (a.customerid) a.customerid,a.firstname,a.lastname,a.contactno,a.email,
			a.address,a.suburb,a.city,a.state,a.landmark,a.doorno,a.postcode,
			a.latitude,a.longitude,a.applocationid,c.locationid as tenantlocationid,a.status
			FROM customers a
			INNER JOIN tenantcustomers c ON a.customerid=c.customerid
			WHERE c.tenantid = ?`

		args = append(args, tid)

		if keyword != "" {
			searchLike := "%" + strings.ToLower(keyword) + "%"
			q1 += ` AND (LOWER(a.firstname) LIKE ? OR LOWER(a.contactno) LIKE ? OR LOWER(a.suburb) LIKE ?)`
			args = append(args, searchLike, searchLike, searchLike)
		}

		q1 += ` ORDER BY a.customerid DESC, c.locationid ASC LIMIT ? OFFSET ?`
		args = append(args, pagesize, offset)
	}

	r.db.Raw(q1, args...).Find(&data)
	return data
}

func (r *customerRepository) SearchCustomer(keyword string, tid int) []models.CustomerInfo {
	var data []models.CustomerInfo
	searchLike := strings.ToLower(keyword) + "%"

	var q1 string
	var args []interface{}

	if tid != 0 {
		q1 = `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email,
			a.address,a.suburb,a.city,a.state,a.landmark,a.doorno,a.postcode,
			a.latitude,a.longitude,a.applocationid,c.locationid as tenantlocationid,a.status
			FROM customers a
			INNER JOIN tenantcustomers c ON a.customerid=c.customerid
			WHERE c.tenantid = ? AND (LOWER(a.firstname) LIKE ? OR LOWER(a.contactno) LIKE ?)`
		args = append(args, tid, searchLike, searchLike)
	} else {
		q1 = `SELECT a.customerid,a.firstname,a.lastname,a.contactno,a.email,
			a.address,a.suburb,a.city,a.state,a.landmark,a.doorno,a.postcode,
			a.latitude,a.longitude,a.applocationid,c.locationid as tenantlocationid,a.status
			FROM customers a
			INNER JOIN tenantcustomers c ON a.customerid=c.customerid
			WHERE (LOWER(a.firstname) LIKE ? OR LOWER(a.contactno) LIKE ?)`
		args = append(args, searchLike, searchLike)
	}

	r.db.Raw(q1, args...).Find(&data)
	return data
}

func (r *customerRepository) CheckCustomer(contactno string) int {
	var data models.Customers
	q1 := `SELECT a.customerid FROM customers a WHERE a.contactno = ?`
	r.db.Raw(q1, contactno).Find(&data)
	return data.Customerid
}

func (r *customerRepository) CheckTenantCustomer(cid, tid int) int {
	var data models.Customers
	q1 := `SELECT a.customerid FROM tenantcustomers a WHERE a.customerid = ? AND a.tenantid = ?`
	r.db.Raw(q1, cid, tid).Find(&data)
	return data.Customerid
}

func (r *customerRepository) CreateTenantCustomer(input models.Customers) int {
	var tcust models.Tenantcustomers
	tcust.Customerid = input.Customerid
	tcust.Tenantid = input.Tenantid

	tx := r.db.Begin()
	if err := tx.Table("tenantcustomers").Create(&tcust).Error; err != nil {
		fmt.Println(err)
		tx.Rollback()
		return 0
	}
	tx.Commit()
	return input.Customerid
}

func (r *customerRepository) CreateCustomer(input models.Customers) int {
	var custloc models.Customerlocations
	var tcust models.Tenantcustomers

	tx := r.db.Begin()
	if err := tx.Create(&input).Error; err != nil {
		fmt.Println(err)
		tx.Rollback()
		return 0
	}

	custloc = models.Customerlocations{
		Customerid:     input.Customerid,
		Address:        input.Address,
		Suburb:         input.Suburb,
		City:           input.City,
		State:          input.State,
		Postcode:       input.Postcode,
		Latitude:       input.Latitude,
		Longitude:      input.Longitude,
		Doorno:         input.Doorno,
		Landmark:       input.Landmark,
		Primaryaddress: 1,
	}
	if err := tx.Table("customerlocations").Create(&custloc).Error; err != nil {
		fmt.Println(err)
		tx.Rollback()
		return 0
	}

	tcust = models.Tenantcustomers{
		Customerid: input.Customerid,
		Tenantid:   input.Tenantid,
	}
	if err := tx.Table("tenantcustomers").Create(&tcust).Error; err != nil {
		fmt.Println(err)
		tx.Rollback()
		return 0
	}

	tx.Commit()
	return input.Customerid
}

func (r *customerRepository) GetCustomerByContactNo(contactNo string) (*models.Customers, error) {
	var customer models.Customers

	query := `
		SELECT a.customerid, a.authmode, a.configid, a.deviceid, a.devicetype, a.customertoken,
			   a.firstname, a.lastname, a.contactno, a.profileimage, a.address, a.suburb,
			   a.city, a.state, a.landmark, a.doorno, a.postcode, a.latitude, a.longitude,
			   a.applocationid, a.status, a.profileimage, a.dialcode, a.intro,
			   b.tenantid, b.locationid,
			   c.qrmode
		FROM customers a
		INNER JOIN tenantcustomers b ON a.customerid = b.customerid
		LEFT JOIN app_location c ON a.applocationid = c.applocationid
		WHERE a.configid = 2 AND a.contactno = ?
	`

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

	return &customer, nil
}
