package customapi

import (
	"encoding/json"
	"errors"
)

// Validation errors
var (
	ErrInvalidImpNoID            = errors.New("CustomRequest: impression ID missing")
	ErrInvalidImpMultiAssets     = errors.New("CustomRequest: impression has multiple assets")
	ErrInvalidVideoNoMimes       = errors.New("CustomRequest: video has no mimes")
	ErrInvalidVideoNoLinearity   = errors.New("CustomRequest: video linearity missing")
	ErrInvalidVideoNoMinDuration = errors.New("CustomRequest: video min-duration missing")
	ErrInvalidVideoNoMaxDuration = errors.New("CustomRequest: video max-duration missing")
	ErrInvalidVideoNoProtocols   = errors.New("CustomRequest: video protocols missing")
)

type CustomRequest struct {
	ID           string        `json:"id"`
	TMax         int           `json:"tmax,omitempty"`
	Dsp          []Dsp         `json:"dsp"`
	Imp          []Impression  `json:"imp,omitempty"`
	Site         *Site         `json:"site,omitempty"`
	App          *App          `json:"app,omitempty"`
	Device       *Device       `json:"device,omitempty"`
	User         *User         `json:"user,omitempty"`
	Regs         *Regulations  `json:"regs,omitempty"`
	Restrictions *Restrictions `json:"restrictions,omitempty"`
	Dooh         *Dooh         `json:"dooh,omitempty"`
}

type CustomResponse struct {
	ID       string  `json:"id"`
	BidID    string  `json:"bidid,omitempty"`
	Currency string  `json:"cur,omitempty"`
	Seat     string  `json:"seat,omitempty"`
	Imp      string  `json:"imp"`
	AdID     string  `json:"adid,omitempty"`
	CID      string  `json:"cid,omitempty"`
	Adm      string  `json:"adm"`
	Price    float64 `json:"price"`
	Bidderid int     `json:"-"`              // Dsp ID
	NURL     string  `json:"-"`              // Win notice URL.
	BURL     string  `json:"burl,omitempty"` // Billing notice URL.
}

type Dsp struct {
	ID             int      `json:"id"`
	Ver            string   `json:"ver"`
	Ping_url       string   `json:"ping_url"`
	AT             int      `json:"at"`
	Cur            []string `json:"cur"`
	Ext            *Ext     `json:"ext,omitempty"`
	Source         *Source  `json:"source,omitempty"`
	Gzip           *int     `json:"gzip"`
	SeconBidBuffer float64  `json:"seconBidBuffer"`
}

type Source struct {
	TID string `json:"tid,omitempty"`
	TS  int64  `json:"ts,omitempty"`
	DS  string `json:"ds,omitempty"`
}

type Ext struct {
	SSP     string   `json:"ssp"`
	At      int      `json:"at,omitempty"`
	Ads_txt *Ads_txt `json:"ads_txt,omitempty"`
	Google  *Google  `json:"google,omitempty"`
}

type Ads_txt struct {
	Status  int    `json:"status"`
	Auth_id string `json:"auth_id"`
	Pub_id  string `json:"pub_id,omitempty"`
}

type Google struct {
	Detected_vertical *Detected_vertical `json:"detected_vertical,omitempty"`
}

type Detected_vertical struct {
	Id     int     `json:"id,omitempty"`
	Weight float64 `json:"weight,omitempty"`
}

type Impression struct {
	ID       string   `json:"id"`
	BidFloor float64  `json:"bidfloor,omitempty"`
	Metric   []Metric `json:"metric,omitempty"`
	Banner   *Banner  `json:"banner,omitempty"`
	Video    *Video   `json:"video,omitempty"`
	Audio    *Audio   `json:"audio,omitempty"`
	Native   *Native  `json:"native,omitempty"`
	Pmp      *Pmp     `json:"pmp,omitempty"`
}

type Pmp struct {
	Private int8            `json:"private_auction,omitempty"`
	Deals   []Deal          `json:"deals,omitempty"`
	Ext     json.RawMessage `json:"ext,omitempty"`
}

type Deal struct {
	ID  string  `json:"id,omitempty"`
	Flr float64 `json:"bidfloor,omitempty"`
}

type Metric struct {
	Type   string          `json:"type"`
	Value  float64         `json:"value,omitempty"`
	Vendor string          `json:"vendor,omitempty"`
	Ext    json.RawMessage `json:"ext,omitempty"`
}

type Banner struct {
	W     int      `json:"w,omitempty"`
	H     int      `json:"h,omitempty"`
	Pos   int      `json:"pos,omitempty"`
	Mimes []string `json:"mimes,omitempty"`
	ID    string   `json:"id,omitempty"`
}

type Video struct {
	Mimes          []string        `json:"mimes,omitempty"`       // Content MIME types supported.
	MinDuration    int             `json:"minduration,omitempty"` // Minimum video ad duration in seconds
	MaxDuration    int             `json:"maxduration,omitempty"` // Maximum video ad duration in seconds
	Protocols      []int           `json:"protocols,omitempty"`   // Video bid response protocols
	Protocol       int             `json:"protocol,omitempty"`    // Video bid response protocols DEPRECATED
	W              int             `json:"w,omitempty"`           // Width of the player in pixels
	H              int             `json:"h,omitempty"`           // Height of the player in pixels
	StartDelay     int             `json:"startdelay,omitempty"`  // Indicates the start delay in seconds
	Linearity      int             `json:"linearity,omitempty"`
	Skip           int             `json:"skip,omitempty"`
	SkipMin        int             `json:"skipmin,omitempty"`
	SkipAfter      int             `json:"skipafter,omitempty"`
	Sequence       int             `json:"sequence,omitempty"`    // Default: 1
	BAttr          []int           `json:"battr,omitempty"`       // Blocked creative attributes
	MaxExtended    int             `json:"maxextended,omitempty"` // Maximum extended video ad duration
	MinBitrate     int             `json:"minbitrate,omitempty"`  // Minimum bit rate in Kbps
	MaxBitrate     int             `json:"maxbitrate,omitempty"`  // Maximum bit rate in Kbps
	BoxingAllowed  *int            `json:"boxingallowed,omitempty"`
	PlaybackMethod []int           `json:"playbackmethod,omitempty"` // List of allowed playback methods
	Delivery       []int           `json:"delivery,omitempty"`       // List of supported delivery methods
	Pos            int             `json:"pos,omitempty"`            // Ad Position
	CompanionAd    []Banner        `json:"companionad,omitempty"`
	Api            []int           `json:"api,omitempty"` // List of supported API frameworks
	CompanionType  []int           `json:"companiontype,omitempty"`
	Placement      int             `json:"placement,omitempty"` // Video placement type
	Ext            json.RawMessage `json:"ext,omitempty"`
}

type Audio struct {
	Mimes         []string        `json:"mimes"`                 // Content MIME types supported.
	MinDuration   int             `json:"minduration,omitempty"` // Minimum video ad duration in seconds
	MaxDuration   int             `json:"maxduration,omitempty"` // Maximum video ad duration in seconds
	Protocols     []int           `json:"protocols,omitempty"`   // Video bid response protocols
	StartDelay    int             `json:"startdelay,omitempty"`  // Indicates the start delay in seconds
	Sequence      int             `json:"sequence,omitempty"`    // Default: 1
	BAttr         []int           `json:"battr,omitempty"`       // Blocked creative attributes
	MaxExtended   int             `json:"maxextended,omitempty"` // Maximum extended video ad duration
	MinBitrate    int             `json:"minbitrate,omitempty"`  // Minimum bit rate in Kbps
	MaxBitrate    int             `json:"maxbitrate,omitempty"`  // Maximum bit rate in Kbps
	Delivery      []int           `json:"delivery,omitempty"`    // List of supported delivery methods
	CompanionAd   []Banner        `json:"companionad,omitempty"`
	API           []int           `json:"api,omitempty"`
	CompanionType []int           `json:"companiontype,omitempty"`
	MaxSequence   int             `json:"maxseq,omitempty"` // The maximumnumber of ads that canbe played in an ad pod.
	Feed          int             `json:"feed,omitempty"`   // Type of audio feed.
	Stitched      int             `json:"stitched,omitempty"`
	NVol          int             `json:"nvol,omitempty"` // Volume normalization mode.
	Ext           json.RawMessage `json:"ext,omitempty"`
}

type Native struct {
	Request *Nativefmt      `json:"request"` // Request payload complying with the Native Ad Specification.
	Ver     string          `json:"ver,omitempty"`
	API     []int           `json:"api,omitempty"`   // List of supported API frameworks for this impression.
	BAttr   []int           `json:"battr,omitempty"` // Blocked creative attributes
	Ext     json.RawMessage `json:"ext,omitempty"`
}

type Nativefmt struct {
	Asset []*Asset `json:"assets"`
}

type Asset struct {
	Id    int    `json:"id"`
	Req   int    `json:"req,omitempty"`
	Title *Title `json:"title,omitempty"`
	Img   *Img   `json:"img,omitempty"`
	Data  *DataN `json:"data,omitempty"`
	Video *Video `json:"video,omitempty"`
}

type Title struct {
	Len int `json:"len,omitempty"`
}

type Img struct {
	Type int      `json:"type,omitempty"`
	W    int      `json:"w,omitempty"`
	Wmin int      `json:"wmin,omitempty"`
	H    int      `json:"h,omitempty"`
	Hmin int      `json:"hmin,omitempty"`
	Mime []string `json:"mime,omitempty"`
}

type DataN struct {
	Type int `json:"type,omitempty"`
	Len  int `json:"len,omitempty"`
}

type Inventory struct {
	ID            string          `json:"id,omitempty"` // ID on the exchange
	Name          string          `json:"name,omitempty"`
	Domain        string          `json:"domain,omitempty"`
	Cat           []string        `json:"cat,omitempty"`           // Array of IAB content categories
	SectionCat    []string        `json:"sectioncat,omitempty"`    // Array of IAB content categories for subsection
	PageCat       []string        `json:"pagecat,omitempty"`       // Array of IAB content categories for page
	PrivacyPolicy *int            `json:"privacypolicy,omitempty"` // Default: 1 ("1": has a privacy policy)
	Publisher     *Publisher      `json:"publisher,omitempty"`     // Details about the Publisher
	Keywords      string          `json:"keywords,omitempty"`      // Comma separated list of keywords about the site.
	Ext           json.RawMessage `json:"ext,omitempty"`
}

type App struct {
	Inventory
	ID            string          `json:"id,omitempty"` // ID on the exchange
	Name          string          `json:"name,omitempty"`
	Domain        string          `json:"domain,omitempty"`
	Cat           []string        `json:"cat,omitempty"`           // Array of IAB content categories
	SectionCat    []string        `json:"sectioncat,omitempty"`    // Array of IAB content categories for subsection
	PageCat       []string        `json:"pagecat,omitempty"`       // Array of IAB content categories for page
	PrivacyPolicy *int            `json:"privacypolicy,omitempty"` // Default: 1 ("1": has a privacy policy)
	Publisher     *Publisher      `json:"publisher,omitempty"`     // Details about the Publisher
	Keywords      string          `json:"keywords,omitempty"`      // Comma separated list of keywords about the site.
	Ext           json.RawMessage `json:"ext,omitempty"`

	Bundle   string `json:"bundle,omitempty"`   // App bundle or package name
	StoreURL string `json:"storeurl,omitempty"` // App store URL for an installed app
	Ver      string `json:"ver,omitempty"`      // App version
	Paid     int    `json:"paid,omitempty"`     // "1": Paid, "2": Free
}

type Site struct {
	Inventory
	ID            string     `json:"id,omitempty"` // ID on the exchange
	Name          string     `json:"name,omitempty"`
	Domain        string     `json:"domain,omitempty"`
	Cat           []string   `json:"cat,omitempty"`           // Array of IAB content categories
	SectionCat    []string   `json:"sectioncat,omitempty"`    // Array of IAB content categories for subsection
	PageCat       []string   `json:"pagecat,omitempty"`       // Array of IAB content categories for page
	PrivacyPolicy *int       `json:"privacypolicy,omitempty"` // Default: 1 ("1": has a privacy policy)
	Publisher     *Publisher `json:"publisher,omitempty"`     // Details about the Publisher
	Keywords      string     `json:"keywords,omitempty"`      // Comma separated list of keywords about the site.
	Ext
	Amp    int    `json:"amp,omitempty"`
	Page   string `json:"page,omitempty"`   // URL of the page
	Ref    string `json:"ref,omitempty"`    // Referrer URL
	Search string `json:"search,omitempty"` // Search string that caused naviation
	Mobile int    `json:"mobile,omitempty"` // Mobile ("1": site is mobile optimised)
}

type ThirdParty struct {
	ID     string          `json:"id,omitempty"`
	Name   string          `json:"name,omitempty"`
	Cat    []string        `json:"cat,omitempty"` // Array of IAB content categories
	Domain string          `json:"domain,omitempty"`
	Ext    json.RawMessage `json:"ext,omitempty"`
}

type Publisher ThirdParty

type Device struct {
	UA         string          `json:"ua,omitempty"` // User agent
	Geo        *Geo            `json:"geo,omitempty"`
	DNT        int             `json:"dnt,omitempty"`        // "1": Do not track
	LMT        int             `json:"lmt,omitempty"`        // "1": Limit Ad Tracking
	IP         string          `json:"ip,omitempty"`         // IPv4
	IPv6       string          `json:"ipv6,omitempty"`       // IPv6
	DeviceType int             `json:"devicetype,omitempty"` // The general type of device.
	Make       string          `json:"make,omitempty"`       // Device make
	Model      string          `json:"model,omitempty"`      // Device model
	OS         string          `json:"os,omitempty"`         // Device OS
	OSVer      string          `json:"osv,omitempty"`        // Device OS version
	HwVer      string          `json:"hwv,omitempty"`        // Hardware version of the device (e.g., "5S" for iPhone 5S).
	H          int             `json:"h,omitempty"`          // Physical height of the screen in pixels.
	W          int             `json:"w,omitempty"`          // Physical width of the screen in pixels.
	PPI        int             `json:"ppi,omitempty"`        // Screen size as pixels per linear inch.
	PxRatio    float64         `json:"pxratio,omitempty"`    // The ratio of physical pixels to device independent pixels.
	JS         int             `json:"js,omitempty"`         // Javascript status ("0": Disabled, "1": Enabled)
	GeoFetch   int             `json:"geofetch,omitempty"`
	FlashVer   string          `json:"flashver,omitempty"` // Flash version
	Language   string          `json:"language,omitempty"` // Browser language
	Carrier    string          `json:"carrier,omitempty"`  // Carrier or ISP derived from the IP address
	MCCMNC     string          `json:"mccmnc,omitempty"`
	ConnType   int             `json:"connectiontype,omitempty"` // Network connection type.
	IFA        string          `json:"ifa,omitempty"`            // Native identifier for advertisers
	IDSHA1     string          `json:"didsha1,omitempty"`        // SHA1 hashed device ID
	IDMD5      string          `json:"didmd5,omitempty"`         // MD5 hashed device ID
	PIDSHA1    string          `json:"dpidsha1,omitempty"`       // SHA1 hashed platform device ID
	PIDMD5     string          `json:"dpidmd5,omitempty"`        // MD5 hashed platform device ID
	MacSHA1    string          `json:"macsha1,omitempty"`
	MacMD5     string          `json:"macmd5,omitempty"` // MD5 hashed device ID; IMEI when available, else MEID or ESN
	Ext        json.RawMessage `json:"ext,omitempty"`
}

type Geo struct {
	Lat           float64         `json:"lat,omitempty"`  // Latitude from -90 to 90
	Lon           float64         `json:"lon,omitempty"`  // Longitude from -180 to 180
	Type          int             `json:"type,omitempty"` // Indicate the source of the geo data
	Accuracy      int             `json:"accuracy,omitempty"`
	LastFix       int             `json:"lastfix,omitempty"`
	IPService     int             `json:"ipservice,omitempty"`
	Country       string          `json:"country,omitempty"`       // Country using ISO 3166-1 Alpha 3
	Region        string          `json:"region,omitempty"`        // Region using ISO 3166-2
	RegionFIPS104 string          `json:"regionFIPS104,omitempty"` // Region of a country using FIPS 10-4
	Metro         string          `json:"metro,omitempty"`
	City          string          `json:"city,omitempty"`
	Zip           string          `json:"zip,omitempty"`
	UTCOffset     int             `json:"utcoffset,omitempty"` // Local time as the number +/- of minutes from UTC
	Ext           json.RawMessage `json:"ext,omitempty"`
}

type User struct {
	ID         string          `json:"id,omitempty"`      // Unique consumer ID of this user on the exchange
	Consent    string          `json:"consent,omitempty"` // Unique consumer ID of this user on the exchange
	BuyerID    string          `json:"buyerid,omitempty"`
	BuyerUID   string          `json:"buyeruid,omitempty"`
	YOB        int             `json:"yob,omitempty"`      // Year of birth as a 4-digit integer.
	Gender     string          `json:"gender,omitempty"`   // Gender ("M": male, "F" female, "O" Other)
	Keywords   string          `json:"keywords,omitempty"` // Comma separated list of keywords, interests, or intent
	CustomData string          `json:"customdata,omitempty"`
	Geo        *Geo            `json:"geo,omitempty"`
	Data       []Data          `json:"data,omitempty"`
	Ext        json.RawMessage `json:"ext,omitempty"`
}

type Data struct {
	ID      string          `json:"id,omitempty"`
	Name    string          `json:"name,omitempty"`
	Segment []Segment       `json:"segment,omitempty"`
	Ext     json.RawMessage `json:"ext,omitempty"`
}

type Segment struct {
	ID    string          `json:"id,omitempty"`
	Name  string          `json:"name,omitempty"`
	Value string          `json:"value,omitempty"`
	Ext   json.RawMessage `json:"ext,omitempty"`
}

type Regulations struct {
	Coppa int             `json:"coppa,omitempty"`
	Gdpr  int             `json:"gdpr,omitempty"`
	Ccpa  string          `json:"ccpa,omitempty"`
	Ext   json.RawMessage `json:"ext,omitempty"`
}

type Restrictions struct {
	Cattax int             `json:"cattax,omitempty"`
	Secure int             `json:"secure,omitempty"`
	Test   int             `json:"secure,omitempty"`
	Bcat   []string        `json:"bcat,omitempty"`
	Badv   []string        `json:"badv,omitempty"`
	Bapp   []string        `json:"bapp,omitempty"`
	Battr  []int           `json:"battr,omitempty"`
	Ext    json.RawMessage `json:"ext,omitempty"`
}

type Dooh struct {
	ID    string     `json:"id,omitempty"`
	Pub   *Publisher `json:"pub,omitempty"`
	Fixed int        `json:"fixed,omitempty"`
	Etime int        `json:"etime,omitempty"`
}

func (imp *Impression) assetCount() int {
	n := 0
	if imp.Banner != nil {
		n++
	}
	if imp.Video != nil {
		n++
	}
	if imp.Native != nil {
		n++
	}
	return n
}

// Validates the `imp` object
func (imp *Impression) Validate() error {
	if imp.ID == "" {
		return ErrInvalidImpNoID
	}

	if count := imp.assetCount(); count > 1 {
		return ErrInvalidImpMultiAssets
	}

	if imp.Video != nil {
		if err := imp.Video.Validate(); err != nil {
			return err
		}
	}

	return nil
}

// Validates the object
func (v *Video) Validate() error {
	if len(v.Mimes) == 0 {
		return ErrInvalidVideoNoMimes
	} else if v.Protocol == 0 && len(v.Protocols) == 0 {
		return ErrInvalidVideoNoProtocols
	}
	return nil
}
